All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.geolatte.geom.curve.MortonCode Maven / Gradle / Ivy

Go to download

This geoLatte-geom library offers a geometry model that conforms to the OGC Simple Features for SQL specification.

The newest version!
/*
 * This file is part of the GeoLatte project.
 *
 *     GeoLatte is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     GeoLatte is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with GeoLatte.  If not, see .
 *
 * Copyright (C) 2010 - 2013 and Ownership of code is shared by:
 * Qmino bvba - Romeinsestraat 18 - 3001 Heverlee  (http://www.qmino.com)
 * Geovise bvba - Generaal Eisenhowerlei 9 - 2140 Antwerpen (http://www.geovise.com)
 */

package org.geolatte.geom.curve;

import org.geolatte.geom.Envelope;
import org.geolatte.geom.Geometry;
import org.geolatte.geom.C2D;
import org.geolatte.geom.Position;
import org.geolatte.geom.crs.CoordinateReferenceSystem;

import java.util.regex.Pattern;

/**
 * Calculates the Morton code (Morton-order or Z-order) of Geometries
 *
 * 

Morton codes are labels for the nodes of a QuadTree. A QuadTree is a partition of a spatial extent by recursively * decomposing it into four equal quadrants. A QuadTree is determined by a spatial extent and depth of the tree (the * number of recursive subdivisions of the extent). Both are specified by the {@code MortonContext} passed during * construction of instances of this class.

* *

The Morton code of a {@code Geometry} can be viewed as a path to the quadrant containing the envelope of that * {@code Geometry}. The left-most character of the code contains the label of the quadrant at depth 1, the second at * depth 2, etc. If the Morton code is the empty string, then the envelope fits in no single quandrant of the QuadTree. *

* *

At each level the four quadrants are labeled:

*
    *
  • O : lower-left quadrant
  • *
  • 1 : upper-left quadrant
  • *
  • 2 : lower-right quadrant
  • *
  • 3 : upper-right quadrant
  • * *
* * @author Karel Maesen, Geovise BVBA * creation-date: 2/19/13 */ public class MortonCode

{ private final Pattern VALID_MORTONCODE_PATTERN = Pattern.compile("[0,1,2,3]*"); private final MortonContext

mortonContext; /** * The width of the leaves of the quadtree implied by the MortonContext */ private final double gridWidth; /** * The height of the leaves of the quadtree implied by the MortonContext */ private final double gridHeight; private final int maxGridCellCoordinate; /** * Constructs an instance with the given {@code Mortoncontext} * *

The specified {@code MortonCode} determines a QuadTree for which * this instance calculates labels. * * @param mortonContext the context to use when calculating morton codes. */ public MortonCode(MortonContext

mortonContext) { this.mortonContext = mortonContext; gridWidth = mortonContext.getLeafWidth(); gridHeight = mortonContext.getLeafHeight(); this.maxGridCellCoordinate = mortonContext.getNumberOfDivisionsAlongAxis() - 1; } /** * Returns the Morton code for the specified {@code Geometry}. * *

This method is equivalent to {@code ofEnvelope(geometry.getEnvelope())}. * * @param geometry a {code Geometry} value. * @return the morton code for the envelope of the specified {@code Geometry}. * @throws IllegalArgumentException if the geometry is null, or has an envelope which is not contained in * the spatial extent of this instance's {@code MortonContext} */ public String ofGeometry(Geometry

geometry) { checkForNull(geometry); return ofEnvelope(geometry.getEnvelope()); } /** * Returns the Morton code for the specified {@code Envelope}. * * @param envelope an {@code Envelope} value. * @return the morton code for the specified {@code Envelope} value. * @throws IllegalArgumentException if the value of the envelope parameter is null, or is not contained in * the spatial extent of this instance's {@code MortonContext} */ public String ofEnvelope(Envelope

envelope) { checkForNull(envelope); checkWithinExtent(envelope); // recalculate the X,Y coordinates to grid-cell coordinates. These are // the row,column-indices of grid formed by the (lowest-level) leaves // of the Quadtree int colMin = getCol(envelope.lowerLeft().getCoordinate(0)); int rowMin = getRow(envelope.lowerLeft().getCoordinate(1)); int colMax = getCol(envelope.upperRight().getCoordinate(0)); int rowMax = getRow(envelope.upperRight().getCoordinate(1)); int[] cols = {colMin, colMax}; int[] rows = {rowMin, rowMax}; // interleave the binary representation of the grid-cell coordinates long[] interLeaved = {0L, 0L}; for (int i = 0; i < 2; i++) { interLeaved[i] = interleave(cols[i], rows[i]); } //return the common prefix return commonMortonCodePrefixAsString(interLeaved[0], interLeaved[1]); } /** * Returns the Morton code for the specified {@code Point}. * * @param pos an {@code Point} value. * @return the morton code for the specified {@code Point} value. * @throws IllegalArgumentException if the value of the point parameter is null, or is not contained in * the spatial extent of this instance's {@code MortonContext} */ public String ofPosition(P pos) { //check inputs checkForNull(pos); checkWithinExtent(pos); int col = getCol(pos.getX()); int row = getRow(pos.getY()); long interleaved = interleave(col, row); return pointMortonCodeAsString(interleaved); } /** * Returns the maximum length of a morton code generated by this instance. * @return the maximum length of a morton code generated by this instance. */ public int getMaxLength() { return this.mortonContext.getDepth(); } /** * Returns the extent that corresponds to the specified morton code * @param mortoncode a morton code String * @return the {@code} envelope representing the specified morton code */ public Envelope

envelopeOf(String mortoncode) { if (mortoncode == null || ! isValidMortonCode(mortoncode)) { throw new IllegalArgumentException(String.format( "Parameter %s is not a valid mortoncode with max. depth %d.", mortoncode, mortonContext.getDepth())); } return envelopeOf(mortoncode, 0, mortonContext.getExtent()); } /* A recursive procedure for calculating the envelope of a mortoncode. We recurse on index into the morton code (in this way we do not have to take apart the mortoncode String) */ private Envelope

envelopeOf(String mortoncode, int index, Envelope

extent) { assert (extent != null); if (index >= mortoncode.length()) { return extent; } char c = mortoncode.charAt(index); double minX = extent.lowerLeft().getCoordinate(0); double minY = extent.lowerLeft().getCoordinate(1); double w = (extent.extentAlongDimension(0)) / 2.0; double h = (extent.extentAlongDimension(1)) / 2.0; CoordinateReferenceSystem

crs = extent.getCoordinateReferenceSystem(); switch (c) { case '0': return envelopeOf(mortoncode, ++index, new Envelope

(minX, minY, minX + w, minY + h, crs)); case '1': return envelopeOf(mortoncode, ++index, new Envelope

(minX, minY + h, minX + w, minY + 2 * h, crs)); case '2': return envelopeOf(mortoncode, ++index, new Envelope

(minX + w, minY, minX + 2 * w, minY + h, crs)); case '3': return envelopeOf(mortoncode, ++index, new Envelope

(minX + w, minY + h, minX + 2 * w, minY + 2 * h, crs)); default: //this can only be a programming error! throw new IllegalStateException("Received a mortoncode element that is not 0, 1, 2 or 3."); } } private boolean isValidMortonCode(String mortoncode) { return (mortoncode.length() <= mortonContext.getDepth()) && VALID_MORTONCODE_PATTERN.matcher(mortoncode).matches(); } private int getRow(double y) { int col = (int) Math.floor((y - mortonContext.getMinY()) / gridHeight); //if col > mortonContext.getMaxGridNum(), then it should fall in the last column // this happens only for coordinates that are exactly equal mortonContext.getMaxY() // since we test for containment in the extent. return col > maxGridCellCoordinate ? col - 1 : col; } private int getCol(double x) { int row = (int) Math.floor((x - mortonContext.getMinX()) / gridWidth); //if col > mortonContext.getMaxGridNum(), then it should fall in the last row // this happens only for coordinates that are exactly equal mortonContext.getMaxX() // since we test for containment in the extent. return row > maxGridCellCoordinate ? row - 1 : row; } private void checkWithinExtent(P pos) { if (!mortonContext.extentContains(pos)) { throw new IllegalArgumentException("Point not in extent of this MortonCodeContext."); } } private void checkWithinExtent(Envelope

envelope) { if (!mortonContext.extentContains(envelope)) { throw new IllegalArgumentException("Geometry envelope not in extent of this MortonCodeContext."); } } //interleaves the bits of col and row integer coordinates. //this is also a mortoncode private long interleave(int col, int row) { long interleaved = 0L; //we only need to interleave up to depth of the tree for (int i = 0; i < mortonContext.getDepth(); i++) { interleaved |= ((row & (1 << i)) << i) | ((col & (1 << i)) << (i + 1)); } return interleaved; } private String pointMortonCodeAsString(long interleaved) { //level is in this case the depth of the QuadTree. return toRadix4String(interleaved, mortonContext.getDepth()); } /** * Returns the common prefix of two morton codes. * *

The common prefix is the morton code for the quadtree node that * is the common ancestor node for the nodes specified by the argument morton codes.

* * @param mc1 first mortonCode (as an interleaved long) * @param mc2 second mortonCode * @return the common prefix of the specified morton codes */ private String commonMortonCodePrefixAsString(long mc1, long mc2) { int commonPrefixLength = 0; int firstSignificantBit = mortonContext.getDepth() * 2; //the bit-mask for the label (in base-4) of the node at a certain tree-depth // we start at the max depth, so first shift the mask to the firstSignificantBit long mask = 3L << firstSignificantBit - 2; for (int level = 1; level <= mortonContext.getDepth(); level++) { // if the base-4 bits when XOR-ed equal to 0 they are the same if (((mc1 & mask) ^ (mc2 & mask)) != 0) { break; } commonPrefixLength = level; mask >>= 2; //right-shift the mask; } //drop all bits to the left of commonPrefixLength * 2 mc1 >>= firstSignificantBit - (commonPrefixLength * 2L); return toRadix4String(mc1, commonPrefixLength); } /** * Transforms the morton code long value into a string such that each character is a label for the quadrant. * * (note: {@code Long.toString(interleaved, 4)} was not used since this turns morton code '001' into '1') * * @param interleaved the morton code as a long * @param level the level of the morton code * @return a String representation of the MortonCode */ private String toRadix4String(long interleaved, int level) { char[] cbuf = new char[level]; for (int pos = level - 1; pos >= 0; pos--) { int label = (int) interleaved & 3; // the label at tree-depth of pos; cbuf[pos] = (char) ('0' + label); interleaved >>= 2; } return String.valueOf(cbuf); } private void checkForNull(Object object) { if (object == null) { throw new IllegalArgumentException("Null geometry is not allowed."); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy