Address to site: parcel area, distances and shares
For a point and a set of open-data layers, works out the parcel's area, the distance to nearby buildings and the share of the parcel in each zoning district, soil unit and flood zone.
How the precision is kept
Arithmetic is 64-bit, but three choices set the accuracy. Longitude and latitude are turned into meters with a flat factor of 111320 m per degree and the cosine of the query latitude. Polygon areas use the shoelace sum on the coordinates as read, without shifting to a local origin, so digits cancel. Shares are counted on a 40 by 40 grid of sample points over the parcel's bounding box.
Open item. Subtract the first vertex from every vertex before the shoelace sum in ringArea so the products are small and the area keeps its digits.
Calculations under this policy
6 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.
The longitude and latitude box that holds a radius in meters around a point.
Δlat = r ÷ 111320; Δlon = r ÷ (111320 × max(0.01, cos lat))
64-bit, with the typed figure 111320 m per degree for both directions and the cosine floored at 0.01. It is a search window, so the approximation only widens or narrows what is looked at.
The shoelace sum on the coordinates as read, with no shift to a local origin. Each term subtracts two products of the size of the coordinates squared, while the area is far smaller, so leading digits cancel. For a parcel about 30 m across given in longitude and latitude near 122° and 38°, the products are about 3 × 10¹⁰ times twice the area, which leaves about 5 significant digits.
site-sources.js
line 88
CivilCraft 0.8.1
const ringArea = (r) => { let a = 0; for (let i = 0, n = r.length; i < n; i++) { const p = r[i], q = r[(i + 1) % n]; a += p[0] * q[1] - q[0] * p[1]; } return a / 2; };
Polygon area in square meters
64-bit
The area of a polygon with holes, scaled to square meters.
A = (Σ |outer ring| − Σ |holes|) × kx × ky
Ring areas are added and subtracted in 64-bit and multiplied by the two meters-per-unit factors. In longitude and latitude those are 111320 × cos(latitude of the query point) and 111320, a flat approximation applied to the whole polygon.
site-sources.js
line 94
CivilCraft 0.8.1
function geomArea(g, kx, ky) { let a = 0; for (const poly of g.c) { a += Math.abs(ringArea(poly[0])); for (const h of poly.slice(1)) a -= Math.abs(ringArea(h)); } return a * kx * ky; }
Meters per degree at the site
64-bit
The factors that turn longitude and latitude differences into meters, and the search window.
kx = 111320 × cos(lat); ky = 111320
One cosine at the query point's latitude in 64-bit. 111320 is a typed spherical figure; the length of a degree on the ellipsoid differs from it by up to about 0.7 % depending on latitude and direction, and the code does not correct for that.
site-sources.js
lines 101 to 103
CivilCraft 0.8.1
const kx = geographic ? 111320 * Math.cos((y * Math.PI) / 180) : 1, ky = geographic ? 111320 : 1;
const r = geographic ? { dx: radius / kx, dy: radius / ky } : { dx: radius, dy: radius };
const area = { minX: x - r.dx, minY: y - r.dy, maxX: x + r.dx, maxY: y + r.dy };
Distance to each building
64-bit
The distance from the point to each building and the building's footprint area.
d = √(((cx − x)·kx)² + ((cy − y)·ky)²), c = center of the building's bounding box
Math.hypot of the scaled differences. The distance is to the center of the building's bounding box, not to its nearest wall or its true centroid.
site-sources.js
lines 120 to 126
CivilCraft 0.8.1
for (const { f, over } of feats('buildings')) {
const c = centroid(f.g), d = Math.hypot((c.x - x) * kx, (c.y - y) * ky);
if (d > radius) continue;
const rec = mapFeature('buildings', f.p, over), levels = num(rec.levels);
site.buildings.push({ record: { height: num(rec.height), levels, kind: text(rec.kind), name: text(rec.name) }, area: geomArea(f.g, kx, ky), d, x: c.x, y: c.y });
}
site.buildings.sort((a, b) => a.d - b.d);
Shares of the parcel by sampling
64-bit
The share of the parcel that lies in each zoning district, soil unit and flood zone.
share = points in the zone ÷ points in the parcel, on a 40 × 40 grid over the parcel's bounding box
A count of sample points, not an area of overlap, so a share can only take steps of one over the number of points that fall inside the parcel. Each point takes the first polygon that contains it. With no parcel, the single query point is the only sample.
site-sources.js
lines 128 to 138
CivilCraft 0.8.1
const samples = [];
if (site.parcel) {
const b = site.parcel.bounds, nx = grid, ny = grid;
for (let j = 0; j < ny; j++) for (let i = 0; i < nx; i++) { const px = b.minX + ((i + 0.5) * (b.maxX - b.minX)) / nx, py = b.minY + ((j + 0.5) * (b.maxY - b.minY)) / ny; if (best.f.g.c.some((poly) => inPoly(px, py, poly))) samples.push([px, py]); }
}
if (!samples.length) samples.push([x, y]);
const share = (kind, key) => {
const list = feats(kind), tally = new Map();
for (const [px, py] of samples) { const hit = list.find(({ f }) => inGeom(px, py, f.g)); const k = hit ? key(hit) : null; if (k !== null) tally.set(k.id, { ...k, n: (tally.get(k.id)?.n || 0) + 1 }); }
return [...tally.values()].sort((a, b) => b.n - a.n).map((v) => ({ ...v, share: v.n / samples.length }));
};
In detail
Method
Shoelace area of rings (holes subtracted) times the two scale factors; even-odd point in polygon; distance to a building's bounding-box center; shares by counting sample points.
Number types
64-bit numbers. Attribute values are read with parseFloat after characters other than digits, point and minus are removed.
Tolerances
The cosine in the search box is floored at 0.01. No tolerance in the point-in-polygon test.
Limits
111320 m per degree is a spherical figure and one cosine is used for the whole site, so areas and distances from longitude and latitude are approximate. A share is a multiple of 1 ÷ (number of sample points inside the parcel), at most 1600 points. Building distance is to the center of the bounding box, not to the footprint.
Approach: 64-bit, stated limit
The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.