From b8b2cdee0c4db03a2108e9632c9942b5d629d019 Mon Sep 17 00:00:00 2001 From: Kyle Barron Date: Fri, 9 Feb 2024 12:58:07 -0500 Subject: [PATCH] Fix polygon layer (#105) * Fix polygon layer * bump beta version --- examples/polygon/app.tsx | 11 ++-- examples/polygon/generate_data.py | 10 +++- package.json | 2 +- src/polygon-layer.ts | 96 +++++++++++++++++++++++++++---- 4 files changed, 99 insertions(+), 20 deletions(-) diff --git a/examples/polygon/app.tsx b/examples/polygon/app.tsx index 1e499d5..0b784cc 100644 --- a/examples/polygon/app.tsx +++ b/examples/polygon/app.tsx @@ -5,11 +5,14 @@ import DeckGL, { Layer, PickingInfo } from "deck.gl/typed"; import { GeoArrowPolygonLayer } from "@geoarrow/deck.gl-layers"; import * as arrow from "apache-arrow"; -const GEOARROW_POLYGON_DATA = "http://localhost:8080/small.feather"; +// const GEOARROW_POLYGON_DATA = "http://localhost:8080/small.feather"; + +const GEOARROW_POLYGON_DATA = "http://localhost:8080/nybb.feather"; const INITIAL_VIEW_STATE = { - latitude: 40.63403641639511, - longitude: -111.91530172951025, + latitude: 40.71, + // longitude: -111.9, + longitude: -74.0, zoom: 9, bearing: 0, pitch: 0, @@ -59,7 +62,7 @@ function Root() { data: table, getFillColor: [0, 100, 60, 160], getLineColor: [255, 0, 0], - lineWidthMinPixels: 0.1, + lineWidthMinPixels: 1, extruded: false, wireframe: true, // getElevation: 0, diff --git a/examples/polygon/generate_data.py b/examples/polygon/generate_data.py index 5e1d681..331ca40 100644 --- a/examples/polygon/generate_data.py +++ b/examples/polygon/generate_data.py @@ -1,12 +1,16 @@ import geopandas as gpd import pyarrow.feather as feather -from lonboard.geoarrow.geopandas_interop import geopandas_to_geoarrow +from lonboard import SolidPolygonLayer def main(): gdf = gpd.read_file("Utah.geojson.zip", engine="pyogrio") - table = geopandas_to_geoarrow(gdf) - feather.write_feather(table, "utah.feather", compression="uncompressed") + layer = SolidPolygonLayer.from_geopandas(gdf) + feather.write_feather(layer.table, "utah.feather", compression="uncompressed") + + gdf = gpd.read_file(gpd.datasets.get_path("nybb")) + layer = SolidPolygonLayer.from_geopandas(gdf) + feather.write_feather(layer.table, "nybb.feather", compression="uncompressed") if __name__ == "__main__": diff --git a/package.json b/package.json index eef0fb9..8a161ce 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "examples/*" ], "name": "@geoarrow/deck.gl-layers", - "version": "0.3.0-beta.11", + "version": "0.3.0-beta.12", "type": "module", "description": "", "source": "src/index.ts", diff --git a/src/polygon-layer.ts b/src/polygon-layer.ts index df385cd..87c1832 100644 --- a/src/polygon-layer.ts +++ b/src/polygon-layer.ts @@ -15,10 +15,82 @@ import { getGeometryVector } from "./utils.js"; import { GeoArrowExtraPickingProps, getPickingInfo } from "./picking.js"; import { ColorAccessor, FloatAccessor, GeoArrowPickingInfo } from "./types.js"; import { EXTENSION_NAME } from "./constants.js"; -import { validateAccessors } from "./validate.js"; import { GeoArrowSolidPolygonLayer } from "./solid-polygon-layer.js"; import { GeoArrowPathLayer } from "./path-layer.js"; +/** + * Get the exterior of a PolygonVector or PolygonData as a MultiLineString + * + * Note that casting to a MultiLineString is a no-op of the underlying data + * structure. For the purposes of the PolygonLayer we don't want to cast to a + * LineString because that would change the number of rows in the table. + */ +export function getPolygonExterior( + input: ga.vector.PolygonVector, +): ga.vector.MultiLineStringVector; +export function getPolygonExterior( + input: ga.data.PolygonData, +): ga.data.MultiLineStringData; + +export function getPolygonExterior( + input: ga.vector.PolygonVector | ga.data.PolygonData, +): ga.vector.MultiLineStringVector | ga.data.MultiLineStringData { + if ("data" in input) { + return new arrow.Vector(input.data.map((data) => getPolygonExterior(data))); + } + + return input; +} + +/** + * Get the exterior of a MultiPolygonVector or MultiPolygonData + * + * Note that for the purposes of the PolygonLayer, we don't want to change the + * number of rows in the table. Instead, we convert each MultiPolygon to a + * single MultiLineString, combining all exteriors of each contained Polygon + * into a single united MultiLineString. + * + * This means that we need to condense both two offset buffers from the + * MultiPolygonVector/Data (geomOffsets and polygonOffsets) into a single + * `geomOffsets` for the new MultiLineStringVector/Data. + */ +export function getMultiPolygonExterior( + input: ga.vector.MultiPolygonVector, +): ga.vector.MultiLineStringVector; +export function getMultiPolygonExterior( + input: ga.data.MultiPolygonData, +): ga.data.MultiLineStringData; + +export function getMultiPolygonExterior( + input: ga.vector.MultiPolygonVector | ga.data.MultiPolygonData, +): ga.vector.MultiLineStringVector | ga.data.MultiLineStringData { + if ("data" in input) { + return new arrow.Vector( + input.data.map((data) => getMultiPolygonExterior(data)), + ); + } + + const geomOffsets: Int32Array = input.valueOffsets; + const polygonData = ga.child.getMultiPolygonChild(input); + const polygonOffsets: Int32Array = polygonData.valueOffsets; + const lineStringData = ga.child.getPolygonChild(polygonData); + + const resolvedOffsets = new Int32Array(geomOffsets.length); + for (let i = 0; i < resolvedOffsets.length; ++i) { + // Perform the lookup + resolvedOffsets[i] = polygonOffsets[geomOffsets[i]]; + } + + return arrow.makeData({ + type: new arrow.List(polygonData.type.children[0]), + length: input.length, + nullCount: input.nullCount, + nullBitmap: input.nullBitmap, + child: lineStringData, + valueOffsets: resolvedOffsets, + }); +} + /** All properties supported by GeoArrowPolygonLayer */ export type GeoArrowPolygonLayerProps = Omit< PolygonLayerProps, @@ -97,6 +169,11 @@ const defaultProps: DefaultProps = { const defaultLineColor: [number, number, number, number] = [0, 0, 0, 255]; const defaultFillColor: [number, number, number, number] = [0, 0, 0, 255]; +/** The `GeoArrowPolygonLayer` renders filled, stroked and/or extruded polygons. + * + * GeoArrowPolygonLayer is a CompositeLayer that wraps the + * GeoArrowSolidPolygonLayer and the GeoArrowPathLayer. + */ export class GeoArrowPolygonLayer< ExtraProps extends {} = {}, > extends CompositeLayer & ExtraProps> { @@ -119,12 +196,12 @@ export class GeoArrowPolygonLayer< return this._renderLayers(polygonVector); } - const MultiPolygonVector = getGeometryVector( + const multiPolygonVector = getGeometryVector( table, EXTENSION_NAME.MULTIPOLYGON, ); - if (MultiPolygonVector !== null) { - return this._renderLayers(MultiPolygonVector); + if (multiPolygonVector !== null) { + return this._renderLayers(multiPolygonVector); } const geometryColumn = this.props.getPolygon; @@ -147,16 +224,11 @@ export class GeoArrowPolygonLayer< ): Layer<{}> | LayersList | null { const { data: table } = this.props; - if (this.props._validate) { - assert(ga.vector.isPolygonVector(geometryColumn)); - validateAccessors(this.props, table); - } - - let getPath: ga.vector.LineStringVector | ga.vector.MultiLineStringVector; + let getPath: ga.vector.MultiLineStringVector; if (ga.vector.isPolygonVector(geometryColumn)) { - getPath = ga.algorithm.getPolygonExterior(geometryColumn); + getPath = getPolygonExterior(geometryColumn); } else if (ga.vector.isMultiPolygonVector(geometryColumn)) { - getPath = ga.algorithm.getMultiPolygonExterior(geometryColumn); + getPath = getMultiPolygonExterior(geometryColumn); } else { assert(false); }