You're trying to uncover all the hidden parcels (polygons) on an ArcGIS map. Click anywhere, and the site gives you back the geometry + attributes for the parcel under your cursor and not much more.
The real problem: How do you systematically discover every polygonal region, given only this point-and-click interface?
What you get on each click (simplified):
{
"geometryType": "esriGeometryPolygon",
"features": [{
"attributes": { "ADDRESS": "..." },
"geometry": { "rings": [ [[x1, y1], [x2, y2], ..., [xN, yN]] ] }
}]
}
(rings form a loop so [x1,y1] == [xN, yN]
Each probe gives you the entire geometry (the ring) of a parcel, as an ArcGIS' Polygon type. Coords are Web Mercator (not lat/lon), so units are big, but you don’t need to brute-force every possible point.
Set a reasonable stride, maybe half the smallest parcel size, and walk the map. Every time you hit a new parcel, save its geometry and skip future probes that land inside it. CPU cycles are cheap; spamming server requests is not.
Here's a toy demo using a simple sweep method: We step through the grid, probe each point, and color new parcels as they're found. Real-world ArcGIS geometries (with rings, holes, etc.) are trickier, but you get the idea.
function createRandomMap(width, height, N, svg) {
svg.innerHTML = "";
const points = Array.from({
length: N
}, () => [
Math.random() * width,
Math.random() * height,
]);
const delaunay = d3.Delaunay.from(points);
const voronoi = delaunay.voronoi([0, 0, width, height]);
const polygons = [];
const svgPolys = [];
for (let i = 0; i < N; ++i) {
const poly = voronoi.cellPolygon(i);
polygons.push(poly);
const el = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
el.setAttribute('points', poly.map(([x, y]) => `${x},${y}`).join(' '));
el.setAttribute('fill', '#fff');
el.setAttribute('stroke', '#222');
el.setAttribute('stroke-width', 1);
svg.appendChild(el);
svgPolys.push(el);
}
return [polygons, svgPolys];
}
// https://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm
function pointInPolygon(polygon, [x, y]) {
let inside = false;
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const [xi, yi] = polygon[i];
const [xj, yj] = polygon[j];
if (
((yi > y) !== (yj > y)) &&
(x < ((xj - xi) * (y - yi)) / (yj - yi) + xi)
) inside = !inside;
}
return inside;
}
async function discoverParcels(polygons, svgPolys, width, height) {
const discovered = new Set();
const paletteGreens = t => `hsl(${100 + 30 * t}, 60%, ${40 + 25 * t}%)`;
for (let y = 0; y < height; ++y) {
for (let x = 0; x < width; ++x) {
for (let i = 0; i < polygons.length; ++i) {
if (!discovered.has(i) && pointInPolygon(polygons[i], [x + 0.5, y + 0.5])) {
discovered.add(i);
svgPolys[i].setAttribute('fill', paletteGreens(i / polygons.length));
await new Promise(r => setTimeout(r, 100));
break;
}
}
}
}
}
const width = 150,
height = 150,
N = 115;
const svg = document.getElementById('voronoi');
async function autoRunLoop() {
while (true) {
let polygons, svgPolys;
[polygons, svgPolys] = createRandomMap(width, height, N, svg);
await discoverParcels(polygons, svgPolys, width, height);
await new Promise(r => setTimeout(r, 2000));
}
}
autoRunLoop();
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdn.jsdelivr.net/npm/d3-delaunay@6"></script>
<style>
body {
background: white;
}
</style>
</head>
<body>
<svg id="voronoi" width="150" height="150"></svg>
</body>
</html>