Inconsistencies in the Open Range Map Dataset

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?

Both images are bird ranges.

  1. Ducula pacifica (taxon id = 3195)

  2. Hydrobates furcatus (taxon id = 1289624)

2 Likes

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!

1 Like

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')
1 Like