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
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 errorWhether 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 errorThe 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 errorAn 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.qweOr 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.geojsonThen, in your program, all of it or only what you call:
use polygons.qweruse polygon_contains, polygon_find from polygons // Quillo 0.6: only theseIt uses these crates, which cargo fetches the first time the plugin is built:
geo = "0.28"geojson = "0.24"Example
// 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
//! 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 })), }))}Related
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.7 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.7 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.7 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.8 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.7 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.7 views · 8 downloadsThread 0 posts
On Polygons: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.