Places

Polygons

Points and areas from GeoJSON: whether a point is in a polygon, which zone holds it, and an area's size, perimeter, centre and bounding box.

Version 1.0.0 · 3 functions · 2 crates

#geo#geojson#maps#delivery#zones

Functions

What use polygons.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use polygon_contains, polygon_find from polygons (Quillo 0.6).

polygon_contains(geojson: any, lat: float, lon: float) → boolor an error

Whether the point is inside any polygon of the GeoJSON (a Polygon, MultiPolygon, Feature or FeatureCollection). A point on the edge is not inside.

polygon_find(geojson: any, lat: float, lon: float) → anyor an error

The properties of the first feature of a FeatureCollection whose geometry holds the point ({ zone: "A", fee: 2.5 }), or null when none does.

polygon_measure(geojson: any) → anyor an error

An area's measures on the Earth (geodesic, WGS 84): { area_km2, perimeter_km, centroid: { lat, lon }, bbox: { min_lat, min_lon, max_lat, max_lon } }, all polygons of the GeoJSON together.

Get it

$ quillo install polygons               # polygons.qwer into this folder$ quillo install polygons --example     # and its example, polygons.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/polygons.qwer$ curl -O https://quillolang.org/plugins/files/polygons.qwe$ curl -O https://quillolang.org/plugins/files/polygons_sample.geojson

Then, in your program, all of it or only what you call:

main.qwe
use polygons.qweruse polygon_contains, polygon_find from polygons      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]
geo = "0.28"geojson = "0.24"

Example

polygons.qwe
// Delivery zones drawn on a map (GeoJSON from geojson.io or QGIS): the fee for an address,// and how big each zone is.////   quillo run polygons.qwe      then open http://localhost:8080/fee?lat=52.373&lon=4.892use "polygons.qwer"let ZONES = json.parse(read_file("polygons_sample.geojson")?)route GET /fee ? { lat: float, lon: float } {    let z = polygon_find(ZONES, lat, lon)?    if z == null { return { delivers: false } }    { delivers: true, zone: z.zone, fee: z.fee, minutes: z.minutes }}route GET /zones = ZONES.features | map((f) => merge({ zone: f.properties.zone }, polygon_measure(f)?))route GET /inside = { centre: polygon_contains(ZONES.features[0], 52.373, 4.892)?, ring_hole: polygon_contains(ZONES.features[1], 52.373, 4.892)? }
$ quillo run polygons.qwe$ curl 'localhost:8080/fee?lat=52.373&lon=4.892'$ curl 'localhost:8080/inside'$ curl 'localhost:8080/zones'

Source

polygons.qwer
//! Points and areas from GeoJSON: is a point inside a polygon (a delivery zone, a city, a store's catchment), which zone holds it, and an area's size in km², perimeter, centre and bounding box.//! version: 1.0.0//!//! [dependencies]//! geo = "0.28"//! geojson = "0.24"use geo::{BoundingRect, Centroid, Contains, GeodesicArea};use serde_json::{json, Value};const MAX_FEATURES: usize = 100_000;/// GeoJSON as given: a map, or its JSON text.fn read(gj: &Value) -> Result<geojson::GeoJson, String> {    let v = match gj {        Value::String(s) => serde_json::from_str(s).map_err(|e| format!("not JSON: {}", e))?,        other => other.clone(),    };    geojson::GeoJson::from_json_value(v).map_err(|e| format!("not GeoJSON: {}", e))}fn geometry(gj: &geojson::GeoJson) -> Result<geo::GeometryCollection<f64>, String> {    geojson::quick_collection(gj).map_err(|e| e.to_string())}fn point(lat: f64, lon: f64) -> Result<geo::Point<f64>, String> {    if !(-90.0..=90.0).contains(&lat) || !(-180.0..=180.0).contains(&lon) {        return Err(format!("{}, {} is not a place", lat, lon));    }    Ok(geo::Point::new(lon, lat))}/// Whether the point is inside any polygon of the GeoJSON (a Polygon, MultiPolygon, Feature or FeatureCollection). A point on the edge is not inside.pub fn polygon_contains(geojson: serde_json::Value, lat: f64, lon: f64) -> Result<bool, String> {    let p = point(lat, lon)?;    Ok(geometry(&read(&geojson)?)?.iter().any(|g| g.contains(&p)))}/// The `properties` of the first feature of a FeatureCollection whose geometry holds the point (`{ zone: "A", fee: 2.5 }`), or null when none does.pub fn polygon_find(geojson: serde_json::Value, lat: f64, lon: f64) -> Result<Value, String> {    let p = point(lat, lon)?;    let geojson::GeoJson::FeatureCollection(fc) = read(&geojson)? else { return Err("polygon_find takes a FeatureCollection".into()) };    if fc.features.len() > MAX_FEATURES {        return Err("at most 100,000 features".into());    }    for f in fc.features {        let Some(g) = f.geometry else { continue };        let g: geo::Geometry<f64> = g.try_into().map_err(|e: geojson::Error| e.to_string())?;        if g.contains(&p) {            return Ok(f.properties.map(Value::Object).unwrap_or(json!({})));        }    }    Ok(Value::Null)}/// An area's measures on the Earth (geodesic, WGS 84): `{ area_km2, perimeter_km, centroid: { lat, lon }, bbox: { min_lat, min_lon, max_lat, max_lon } }`, all polygons of the GeoJSON together.pub fn polygon_measure(geojson: serde_json::Value) -> Result<Value, String> {    let gc = geometry(&read(&geojson)?)?;    let mut area = 0.0;    let mut perim = 0.0;    for g in gc.iter() {        let polys: Vec<geo::Polygon<f64>> = match g {            geo::Geometry::Polygon(p) => vec![p.clone()],            geo::Geometry::MultiPolygon(mp) => mp.0.clone(),            _ => vec![],        };        for p in polys {            let (pe, a) = p.geodesic_perimeter_area_unsigned();            area += a;            perim += pe;        }    }    let c = gc.centroid();    let b = gc.bounding_rect();    let r = |x: f64, d: f64| (x * d).round() / d;    Ok(json!({        "area_km2": r(area / 1e6, 1e4), "perimeter_km": r(perim / 1e3, 1e4),        "centroid": c.map(|c| json!({ "lat": r(c.y(), 1e6), "lon": r(c.x(), 1e6) })),        "bbox": b.map(|b| json!({ "min_lat": b.min().y, "min_lon": b.min().x, "max_lat": b.max().y, "max_lon": b.max().x })),    }))}
H3 hexagons h3.qwer · 1.0.0H3 hexagon cells: the cell of a point at a resolution, its neighbours within k rings, its outline for a map, and distances between cells.geomapshexagonssearchdelivery7 views · 8 downloadsKML and KMZ kml.qwer · 1.0.0KML and KMZ files from Google Earth and My Maps read to GeoJSON, placemarks with their names, descriptions, folders and extended data, and GeoJSON written back as KML or KMZ.geogeojsonmapsimport7 views · 8 downloadsMap clusters map_clusters.qwer · 1.0.0Map markers clustered by zoom as Supercluster does: thousands of points loaded once, then for each view and zoom the clusters with their counts and the single points, a cluster's points, and the zoom where it opens.mapsgeogeojsonperformance7 views · 7 downloadsMap projections projections.qwer · 1.0.0Coordinates moved between map projections offline, by EPSG code or PROJ string: GPS degrees to Web Mercator, UTM, the British, Dutch and Swiss grids and some 6,000 others, for points and whole GeoJSON.geomapsgeojsongps8 views · 9 downloadsPlus codes plus_codes.qwer · 1.0.0Plus codes (Open Location Codes), the addresses Google Maps gives places without a street: from a latitude and longitude, back to their area, shortened near a town and recovered from the short form.geomapsaddressesdelivery7 views · 8 downloadsShapefiles shapefile.qwer · 1.0.0Esri Shapefiles read to GeoJSON with their attributes, from the .shp and .dbf or the .zip they come in, with their fields, bounds and projection; and GeoJSON written as a shapefile for the GIS team.geogeojsonmapsimport7 views · 8 downloads
Thread
78Useful? Like it, or add to its thread below.

Thread 0 posts

On Polygons: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.