I noticed some “inconsistencies” between different ranges from the Open Range Map Dataset.
Some ranges properly overlap the anti-meridian (the 180° meridian in the middle of the Pacific ocean) like in the first picture, and some doesn’t, like in the second one.
In the first one, it seems the hexagons used to generate the range map where properly “cropped”. But in the second one, hexagons seems to have been kept only they didn’t intersect the anti-meridian.
Is it because the model used to generate the dataset is still young and it will be refined as ranges are generated?
Thanks for the feedback. This is a known issue. The ranges as they exist now are our initial attempt to generate the best ranges possible, knowing we have room to improve. The ranges are created by collecting H3 hex cells where the taxa are expected to be present, based on geomodel score. We determine the boundaries of collected cells as the geometry representation of the range of the taxon. Some of these geometries cross the antimeridian.
With GeoJSON it can be tricky to properly represent geometries that cross the antimeridian, so we attempt to split the ranges at the antimeridian so there’s no confusion about directionality with geometries that cross it. This can be really tricky to do when the range is complicated, with lots of nested cutouts, or even range “islands” existing inside gaps in the range. We use the python antimedirian package, described in this paper, to attempt to split the geometries at the antimeridian, but some of the more complicated geometries cannot be split successfully. For these very complicated geometries, we are currently simply excluding the H3 cells which cross the antimeridian, leading to some gaps there as demonstrated on the right edge in your second screenshot.
We hope to eventually find good ways to split even the most complicated geometries at the antimeridian with no gaps, and we expect the precision of these ranges to improve over time. If you have any recommendations of software that can help antimeridian splitting of complicated geometries, we’re always open to suggestions!
Thanks for your answer. I don’t have much experience using H3 hex cells so I don’t really know what it yields in terms of results, whether it only returns the coordinates of the hexagons or the actual completely dissolved geometry.
I’ve written something below using the Shapely Python library along with NumPy and GeoPandas to write the GeoJSON. Shapely is pretty powerful and can easily crop complex geometries.
My script takes already dissolved hexagons in entry, I work here in EPSG:3857 to allow the antimeridian crossing. The purple dataset below is not really data but I gives an idea I think. The idea is that it creates a rectangle using the bounds of the CRS (rectangle1) and a rectangle using the bounds but shifted 180 degree east (rectangle2). It needs to be adjusted if the crossing is west.
Then I use shapely to make two intersections with both rectangle and shift the result of the intersection with rectangle2 180 degrees west, then dissolve the result to get a new multipolygon.
You finally can transform the result in EPSG:4326 and export as GeoJSON.
I don’t know if that helps, that’s what I would do, but then again I don’t know what is provided by the Hex cells library.
import numpy as np
import geopandas as gpd
from shapely import Polygon, intersection
from shapely.affinity import translate
from shapely.ops import unary_union
# Set min and max of EPSG:3857 CRS
minimum = -np.pi * 6378137
maximum = np.pi * 6378137
# Create a rectangle using the bounds of the CRS
rectangle1 = Polygon([
[minimum, minimum],
[minimum, maximum],
[maximum, maximum],
[maximum, minimum]
])
# Create a second rectangle shifted after the 180 degree meridian
rectangle2 = Polygon([
[maximum, minimum],
[maximum, maximum],
[maximum*3, maximum],
[maximum*3, minimum]
])
# Reading the dataset, which should be a GeoJSON of multipolygons
data = gpd.read_file('data.geojson')
result = []
# Loop through GeoDataFrame rows
for row, entry in data.iterrows():
# Get the current row geometry
multipolygon = entry.geometry
# Calculate the intersection of the multipolygon and the bound rectangle
r1_intersection = intersection(multipolygon, rectangle1)
# Calculate the intersection of the multipolygon and the shifted rectangle
r2_intersection = intersection(multipolygon, rectangle2)
# Shift the intersection after the 180 degree meridian by 180 degree west
shifted = translate(r2_intersection, -maximum*2)
# Union (dissolve) both intersection into one new multipolygon
unioned = unary_union([r1_intersection, shifted])
# Add the union to the result container
result.append({ 'geometry': unioned })
# Save as a new GeoJSON using geopandas
gdf = gpd.GeoDataFrame(result, crs=3857)
gdf.to_file('result.geojson', driver='GeoJSON')