fuzzyworld

when-am-i / files / src / utils / geo.ts

geo.ts

Blame: the commit that last changed each line · file · history

a71096e6 2026-06-18 Pavel Safronov1// Point-in-region geometry. GeoJSON coordinates are [lng, lat].
2
3import type { CalendarRegion, Geometry, LinearRing } from '../types';
4
5// Standard ray-casting test against a single ring (x = lng, y = lat).
6function pointInRing(lng: number, lat: number, ring: LinearRing): boolean {
7 let inside = false;
8 for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
9 const [xi, yi] = ring[i];
10 const [xj, yj] = ring[j];
11 const intersects =
12 yi > lat !== yj > lat &&
13 lng < ((xj - xi) * (lat - yi)) / (yj - yi) + xi;
14 if (intersects) inside = !inside;
15 }
16 return inside;
17}
18
19// A polygon is its outer ring (index 0) minus any holes (further rings).
20function pointInPolygonRings(lng: number, lat: number, rings: LinearRing[]): boolean {
21 if (rings.length === 0) return false;
22 const [outer, ...holes] = rings;
23 if (!pointInRing(lng, lat, outer)) return false;
24 return !holes.some((hole) => pointInRing(lng, lat, hole));
25}
26
27/** Whether the point (lat, lng) lies inside a Polygon or MultiPolygon. */
28export function pointInPolygon(lat: number, lng: number, geometry: Geometry): boolean {
29 if (geometry.type === 'Polygon') {
30 return pointInPolygonRings(lng, lat, geometry.coordinates);
31 }
32 return geometry.coordinates.some((polygon) => pointInPolygonRings(lng, lat, polygon));
33}
34
35/** All regions whose geometry contains the point — regions may overlap. */
36export function findRegions(
37 lat: number,
38 lng: number,
39 regions: CalendarRegion[],
40): CalendarRegion[] {
41 return regions.filter((r) => pointInPolygon(lat, lng, r.region.geo));
42}