Advanced Usage
This section covers advanced spatial indexing topics for production use.
Encoding and Precision
The spatial indexer uses fixed-point integer encoding to represent coordinates:
-
The coordinate value is multiplied by 10^7 (e.g.,
51.5074becomes515074000) -
The resulting integer is XOR’d with
0x80000000to produce an order-preserving unsigned representation -
The 4 bytes of the integer are stored as individual sub-index entries
This encoding provides approximately 1.1cm precision, which is more than sufficient for virtually all geographic applications.
| Scale Factor | Precision | Suitable For |
|---|---|---|
10^7 (used) |
~1.1 cm |
All practical applications |
The 8 sub-indices (4 for latitude, 4 for longitude) use the existing HashingCompositeIndexer infrastructure, so no additional storage mechanisms are required.
Antimeridian Handling
The antimeridian (International Date Line at 180/-180 degrees longitude) requires special handling for queries that cross it.
Bounding Box Queries
When minLon > maxLon in a withinBox query, the indexer automatically splits the longitude range into two parts:
// Bounding box crossing the antimeridian (170°E to 170°W)
// Internally becomes: (170 to 180) OR (-180 to -170)
gigaMap.query(locationIndex.withinBox(-20.0, -15.0, 170.0, -170.0));
Proximity Queries
The near method also handles antimeridian wrapping. When the computed bounding box extends past 180 or -180 degrees longitude, it automatically splits the query.
// Proximity search near the antimeridian
// Fiji is at longitude 178.065 - a search centered at 179° will find it
gigaMap.query(locationIndex.near(-17.7134, 179.0, 200.0));
Post-Filtering with withinRadius
The near method returns a bounding box approximation of the circular search area. This means some results in the corners of the bounding box may be outside the actual circle.
For exact circular distance filtering, use the withinRadius predicate as a post-filter on query results.
double centerLat = 40.0;
double centerLon = -74.0;
double radiusKm = 150.0;
// Step 1: Bounding box query (fast, may include false positives in corners)
// Step 2: Post-filter with exact Haversine distance
List<Location> exactResults = gigaMap.query(locationIndex.near(centerLat, centerLon, radiusKm))
.stream()
.filter(locationIndex.withinRadius(centerLat, centerLon, radiusKm))
.toList();
The bounding box query efficiently narrows down candidates using the bitmap index, while the withinRadius predicate applies exact great-circle distance calculation on the reduced result set.
Haversine Distance
The SpatialIndexer.haversineDistance static method computes the great-circle distance between two points on Earth using the Haversine formula.
double distanceKm = SpatialIndexer.haversineDistance(
40.7128, -74.0060, // Point 1: New York
51.5074, -0.1278 // Point 2: London
);
// Returns ~5570 km
The calculation assumes a spherical Earth with radius 6,371 km. This provides sufficient accuracy for most applications, though it does not account for the Earth’s oblateness.
Null Coordinate Handling
Both latitude and longitude must be either null or non-null. Attempting to index an entity with only one coordinate set throws an IllegalArgumentException.
// Valid: both null
gigaMap.add(new Location("Virtual", null, null));
// Valid: both non-null
gigaMap.add(new Location("Berlin", 52.52, 13.405));
// Invalid: throws IllegalArgumentException
gigaMap.add(new Location("Bad", 52.52, null));
Query for entities without coordinates using isNull().
gigaMap.query(locationIndex.isNull());
Boundary Values
The spatial indexer supports the full coordinate range:
-
Latitude: -90.0 (South Pole) to 90.0 (North Pole)
-
Longitude: -180.0 (antimeridian west) to 180.0 (antimeridian east)
// Pole and dateline coordinates work correctly
gigaMap.add(new Location("North Pole", 90.0, 0.0));
gigaMap.add(new Location("South Pole", -90.0, 0.0));
gigaMap.add(new Location("Dateline East", 0.0, 180.0));
gigaMap.add(new Location("Dateline West", 0.0, -180.0));
// Full range queries
gigaMap.query(locationIndex.latitudeBetween(-90.0, 90.0)); // All latitudes
gigaMap.query(locationIndex.longitudeBetween(-180.0, 180.0)); // All longitudes
Composing Complex Queries
All spatial query methods return Condition objects that can be combined using and() and or().
// Northern hemisphere AND eastern hemisphere
gigaMap.query(
locationIndex.latitudeAbove(0.0).and(locationIndex.longitudeAbove(0.0))
);
// Europe OR East Asia
gigaMap.query(
locationIndex.withinBox(35.0, 60.0, -10.0, 30.0)
.or(locationIndex.withinBox(20.0, 50.0, 100.0, 150.0))
);
Implementation Details
The SpatialIndexer extends HashingCompositeIndexer, which means it uses the same bitmap infrastructure as other composite indexers like IndexerLocalDate (year/month/day decomposition) and the byte indexers.
Index Structure
| Sub-Index | Position | Content |
|---|---|---|
Latitude byte 0 |
0 |
Most significant byte of latitude |
Latitude byte 1 |
1 |
Second byte of latitude |
Latitude byte 2 |
2 |
Third byte of latitude |
Latitude byte 3 |
3 |
Least significant byte of latitude |
Longitude byte 0 |
4 |
Most significant byte of longitude |
Longitude byte 1 |
5 |
Second byte of longitude |
Longitude byte 2 |
6 |
Third byte of longitude |
Longitude byte 3 |
7 |
Least significant byte of longitude |
Range queries work by decomposing the bound values into bytes and constructing composite conditions that respect the byte ordering. The XOR encoding ensures that the byte ordering is preserved across negative and positive coordinates.