I’m utilizing react-native-maps
in my project. There’s a functionality on desktop where a user can draw “regions” on the map that have a corresponding color. They should display on mobile.
I’m having an issue with rendering multi-polygons – polygons that are themselves comprised of multiple polygons.
This is Redux project. So to give an example when fetched from the store, simpler regions/polygons exist at
geom:
coordinates: Array(1)
0: Array(1)
0: Array(4606)
0: (2) [-118.997304971, 45.990188871]
// remaining coordinates
....
More complex regions/Multi-polygons exist at
geom:
coordinates: Array(63)
0: Array(1)
0: Array(17)
0: (2) [-124.686066, 47.932503]
....
So you can see that the nesting of the coordinates is different between the two (the coordinates existing at coordinates[0][0]
vs having to map
over coordinates
to get them all. I’m not totally privy to the workings of the backend currently, but basically, the organization of the coordinates is generated from geometries, so this difference is probably something PostGIS did.
THE ISSUE is that I cannot get the multi polygons to render the color correctly. I’ve tried chunking, simplifying the shapes by sampling Nth points or by using the Ramer Douglas Pecker (RDP) algorithm. Using RDP has gotten me some progress, but the issue I’ve run into when trying a simplify the shape approach is that while some parts of the region’s colors appear, they disappear depending on the zoom level of the map, and some polygons in the multi polygon do not show the color regardless of zoom.
How can I handle rendering multi polygons? Or does react-native-maps
just struggle to render multi polygons/ polygons with 20,000, 30,000+ coordinates?
Here’s my current renderPolygon
function:
const renderPolygon = (region) => {
// Helper to calculate point-to-line distance
const pointLineDistance = (point, start, end) => {
const [x, y] = point;
const [x1, y1] = start;
const [x2, y2] = end;
const numerator = Math.abs((y2-y1)*x - (x2-x1)*y + x2*y1 - y2*x1);
const denominator = Math.sqrt((y2-y1)**2 + (x2-x1)**2);
return numerator/denominator;
};
// RDP implementation
const rdpSimplify = (points, epsilon) => {
if (points.length <= 2) return points;
let maxDistance = 0;
let maxIndex = 0;
// Find point with max distance
for (let i = 1; i < points.length - 1; i++) {
const distance = pointLineDistance(
points[i],
points[0],
points[points.length-1]
);
if (distance > maxDistance) {
maxDistance = distance;
maxIndex = i;
}
}
// Recursively simplify if max distance > epsilon
if (maxDistance > epsilon) {
const first = rdpSimplify(points.slice(0, maxIndex + 1), epsilon);
const second = rdpSimplify(points.slice(maxIndex), epsilon);
return [...first.slice(0, -1), ...second];
}
return [points[0], points[points.length-1]];
};
const simplifyCoords = (coords) => {
// Only simplify if more than 1000 points
if (coords.length <= 1000) {
return [...coords];
}
// Dynamic epsilon based on coordinate count
const epsilon = coords.length > 10000 ? 0.001 : 0.0001;
// Apply RDP while ensuring closure
const simplified = rdpSimplify(coords.slice(0, -1), epsilon);
simplified.push(simplified[0]); // Close polygon
return simplified;
};
const toLatLng = (coords) =>
coords.map((coord) => ({
latitude: Number(coord[1]),
longitude: Number(coord[0]),
}));
const { coordinates } = region.geom;
// Rest of the render logic remains the same...
if (coordinates.length > 1) {
return coordinates.map((polyCoords, index) => {
const coords = polyCoords[0];
const simplified = simplifyCoords(coords);
return (
<Polygon
key={`${region.id}-${index}`}
coordinates={toLatLng(simplified)}
fillColor={hexToRgba(region.color, 0.5)}
strokeColor={"#FFFFFF"}
strokeWidth={2}
tappable={true}
geodesic={true}
zIndex={100}
/>
);
});
}
const coords = coordinates[0][0];
return (
<Polygon
key={region.id}
coordinates={toLatLng(coords)}
fillColor={hexToRgba(region.color, 0.5)}
strokeColor={"#FFFFFF"}
strokeWidth={2}
tappable={true}
geodesic={true}
zIndex={100}
/>
);
};