UNPKG

@loaders.gl/gis

Version:

Helpers for GIS category data

5 lines 68.5 kB
{ "version": 3, "sources": ["index.js", "lib/geo/geoparquet-metadata-schema.js", "lib/geo/geoparquet-metadata.js", "lib/geo/geoarrow-metadata.js", "lib/tables/convert-table-to-geojson.js", "lib/binary-features/flat-geojson-to-binary.js", "lib/binary-features/extract-geometry-info.js", "lib/binary-features/geojson-to-flat-geojson.js", "lib/binary-features/geojson-to-binary.js", "lib/binary-features/binary-to-geojson.js", "lib/binary-features/transform.js"], "sourcesContent": ["// Types from `@loaders.gl/schema`\n// Geo Metadata\n// import {default as GEOPARQUET_METADATA_SCHEMA} from './lib/geo/geoparquet-metadata-schema.json';\n// export {GEOPARQUET_METADATA_SCHEMA};\nexport { GEOPARQUET_METADATA_JSON_SCHEMA } from \"./lib/geo/geoparquet-metadata-schema.js\";\nexport { getGeoMetadata, setGeoMetadata, unpackGeoMetadata } from \"./lib/geo/geoparquet-metadata.js\";\nexport { unpackJSONStringMetadata } from \"./lib/geo/geoparquet-metadata.js\";\nexport { getGeometryColumnsFromSchema } from \"./lib/geo/geoarrow-metadata.js\";\n// Table conversion\nexport { convertWKBTableToGeoJSON } from \"./lib/tables/convert-table-to-geojson.js\";\n// Binary Geometries\nexport { flatGeojsonToBinary } from \"./lib/binary-features/flat-geojson-to-binary.js\";\nexport { geojsonToBinary } from \"./lib/binary-features/geojson-to-binary.js\";\nexport { geojsonToFlatGeojson } from \"./lib/binary-features/geojson-to-flat-geojson.js\";\nexport { binaryToGeojson, binaryToGeometry } from \"./lib/binary-features/binary-to-geojson.js\";\nexport { transformBinaryCoords, transformGeoJsonCoords } from \"./lib/binary-features/transform.js\";\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n/* eslint-disable camelcase */\n/**\n * Geoparquet JSON schema for geo metadata\n * @see https://github.com/geoarrow/geoarrow/blob/main/metadata.md\n * @see https://github.com/opengeospatial/geoparquet/blob/main/format-specs/geoparquet.md\n */\nexport const GEOPARQUET_METADATA_JSON_SCHEMA = {\n $schema: 'http://json-schema.org/draft-07/schema#',\n title: 'GeoParquet',\n description: 'Parquet metadata included in the geo field.',\n type: 'object',\n required: ['version', 'primary_column', 'columns'],\n properties: {\n version: { type: 'string', const: '1.0.0-beta.1' },\n primary_column: { type: 'string', minLength: 1 },\n columns: {\n type: 'object',\n minProperties: 1,\n patternProperties: {\n '.+': {\n type: 'object',\n required: ['encoding', 'geometry_types'],\n properties: {\n encoding: { type: 'string', const: 'WKB' },\n geometry_types: {\n type: 'array',\n uniqueItems: true,\n items: {\n type: 'string',\n pattern: '^(GeometryCollection|(Multi)?(Point|LineString|Polygon))( Z)?$'\n }\n },\n crs: {\n oneOf: [\n {\n $ref: 'https://proj.org/schemas/v0.5/projjson.schema.json'\n },\n { type: 'null' }\n ]\n },\n edges: { type: 'string', enum: ['planar', 'spherical'] },\n orientation: { type: 'string', const: 'counterclockwise' },\n bbox: {\n type: 'array',\n items: { type: 'number' },\n oneOf: [\n {\n description: '2D bbox consisting of (xmin, ymin, xmax, ymax)',\n minItems: 4,\n maxItems: 4\n },\n {\n description: '3D bbox consisting of (xmin, ymin, zmin, xmax, ymax, zmax)',\n minItems: 6,\n maxItems: 6\n }\n ]\n },\n epoch: { type: 'number' }\n }\n }\n },\n additionalProperties: false\n }\n }\n};\n", "// GEO METADATA\n/**\n * Reads the GeoMetadata object from the metadata\n * @note geoarrow / parquet schema is stringified into a single key-value pair in the parquet metadata\n */\nexport function getGeoMetadata(schema) {\n const geoMetadata = parseJSONStringMetadata(schema, 'geo');\n if (!geoMetadata) {\n return null;\n }\n for (const column of Object.values(geoMetadata.columns || {})) {\n if (column.encoding) {\n column.encoding = column.encoding.toLowerCase();\n }\n }\n return geoMetadata;\n}\n/**\n * Stores a geoarrow / geoparquet geo metadata object in the schema\n * @note geoarrow / geoparquet geo metadata is a single stringified JSON field\n */\nexport function setGeoMetadata(schema, geoMetadata) {\n const stringifiedGeoMetadata = JSON.stringify(geoMetadata);\n schema.metadata.geo = stringifiedGeoMetadata;\n}\n/**\n * Unpacks geo metadata into separate metadata fields (parses the long JSON string)\n * @note geoarrow / parquet schema is stringified into a single key-value pair in the parquet metadata\n */\nexport function unpackGeoMetadata(schema) {\n const geoMetadata = getGeoMetadata(schema);\n if (!geoMetadata) {\n return;\n }\n // Store Parquet Schema Level Metadata\n const { version, primary_column, columns } = geoMetadata;\n if (version) {\n schema.metadata['geo.version'] = version;\n }\n if (primary_column) {\n schema.metadata['geo.primary_column'] = primary_column;\n }\n // store column names as comma separated list\n schema.metadata['geo.columns'] = Object.keys(columns || {}).join('');\n for (const [columnName, columnMetadata] of Object.entries(columns || {})) {\n const field = schema.fields.find((field) => field.name === columnName);\n if (field) {\n if (field.name === primary_column) {\n setFieldMetadata(field, 'geo.primary_field', 'true');\n }\n unpackGeoFieldMetadata(field, columnMetadata);\n }\n }\n}\n// eslint-disable-next-line complexity\nfunction unpackGeoFieldMetadata(field, columnMetadata) {\n for (const [key, value] of Object.entries(columnMetadata || {})) {\n switch (key) {\n case 'geometry_types':\n setFieldMetadata(field, `geo.${key}`, value.join(','));\n break;\n case 'bbox':\n setFieldMetadata(field, `geo.crs.${key}`, JSON.stringify(value));\n break;\n case 'crs':\n // @ts-ignore\n for (const [crsKey, crsValue] of Object.entries(value || {})) {\n switch (crsKey) {\n case 'id':\n // prettier-ignore\n const crsId = typeof crsValue === 'object'\n ? // @ts-ignore\n `${crsValue?.authority}:${crsValue?.code}`\n : JSON.stringify(crsValue);\n setFieldMetadata(field, `geo.crs.${crsKey}`, crsId);\n break;\n default:\n setFieldMetadata(field, `geo.crs.${crsKey}`, typeof crsValue === 'string' ? crsValue : JSON.stringify(crsValue));\n break;\n }\n }\n break;\n case 'edges':\n default:\n setFieldMetadata(field, `geo.${key}`, typeof value === 'string' ? value : JSON.stringify(value));\n }\n }\n}\nfunction setFieldMetadata(field, key, value) {\n field.metadata = field.metadata || {};\n field.metadata[key] = value;\n}\n// HELPERS\n/** Parse a key with stringified arrow metadata */\nexport function parseJSONStringMetadata(schema, metadataKey) {\n const stringifiedMetadata = schema.metadata[metadataKey];\n if (!stringifiedMetadata) {\n return null;\n }\n try {\n const metadata = JSON.parse(stringifiedMetadata);\n if (!metadata || typeof metadata !== 'object') {\n return null;\n }\n return metadata;\n }\n catch {\n return null;\n }\n}\nexport function unpackJSONStringMetadata(schema, metadataKey) {\n const json = parseJSONStringMetadata(schema, metadataKey);\n for (const [key, value] of Object.entries(json || {})) {\n schema.metadata[`${metadataKey}.${key}`] =\n typeof value === 'string' ? value : JSON.stringify(value);\n }\n}\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n/** Array containing all encodings */\nconst GEOARROW_ENCODINGS = [\n 'geoarrow.multipolygon',\n 'geoarrow.polygon',\n 'geoarrow.multilinestring',\n 'geoarrow.linestring',\n 'geoarrow.multipoint',\n 'geoarrow.point',\n 'geoarrow.wkb',\n 'geoarrow.wkt'\n];\nconst GEOARROW_COLUMN_METADATA_ENCODING = 'ARROW:extension:name';\nconst GEOARROW_COLUMN_METADATA_METADATA = 'ARROW:extension:metadata';\n/**\n * get geometry columns from arrow table\n */\nexport function getGeometryColumnsFromSchema(schema) {\n const geometryColumns = {};\n for (const field of schema.fields) {\n const metadata = getGeometryMetadataForField(field);\n if (metadata) {\n geometryColumns[field.name] = metadata;\n }\n }\n return geometryColumns;\n}\n/**\n * Extracts GeoArrow metadata from a field\n * @param field\n * @returns\n * @see https://github.com/geoarrow/geoarrow/blob/d2f56704414d9ae71e8a5170a8671343ed15eefe/extension-types.md\n */\nexport function getGeometryMetadataForField(field) {\n let metadata = null;\n // Check for GeoArrow column encoding\n let geoEncoding = field.metadata?.[GEOARROW_COLUMN_METADATA_ENCODING];\n if (geoEncoding) {\n geoEncoding = geoEncoding.toLowerCase();\n // at time of testing, ogr2ogr uses WKB/WKT for encoding.\n if (geoEncoding === 'wkb') {\n geoEncoding = 'geoarrow.wkb';\n }\n if (geoEncoding === 'wkt') {\n geoEncoding = 'geoarrow.wkt';\n }\n if (!GEOARROW_ENCODINGS.includes(geoEncoding)) {\n // eslint-disable-next-line no-console\n console.warn(`Invalid GeoArrow encoding: ${geoEncoding}`);\n }\n else {\n metadata = metadata || {};\n metadata.encoding = geoEncoding;\n }\n }\n // Check for GeoArrow metadata\n const columnMetadata = field.metadata?.[GEOARROW_COLUMN_METADATA_METADATA];\n if (columnMetadata) {\n try {\n metadata = JSON.parse(columnMetadata);\n }\n catch (error) {\n // eslint-disable-next-line no-console\n console.warn('Failed to parse GeoArrow metadata', error);\n }\n }\n return metadata || null;\n}\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\nimport { getTableLength, getTableRowAsObject } from '@loaders.gl/schema';\nimport { getGeoMetadata } from \"../geo/geoparquet-metadata.js\";\n/** TODO - move to loaders.gl/gis? */\nexport function convertWKBTableToGeoJSON(table, schema, loaders) {\n const geoMetadata = getGeoMetadata(schema);\n const primaryColumn = geoMetadata?.primary_column;\n if (!primaryColumn) {\n throw new Error('no geometry column');\n }\n const columnMetadata = geoMetadata.columns[primaryColumn];\n const features = [];\n const length = getTableLength(table);\n for (let rowIndex = 0; rowIndex < length; rowIndex++) {\n const row = getTableRowAsObject(table, rowIndex);\n const geometry = parseGeometry(row[primaryColumn], columnMetadata, loaders);\n delete row[primaryColumn];\n const feature = { type: 'Feature', geometry: geometry, properties: row };\n features.push(feature);\n }\n return { shape: 'geojson-table', schema, type: 'FeatureCollection', features };\n}\nfunction parseGeometry(geometry, columnMetadata, loaders) {\n switch (columnMetadata.encoding) {\n case 'wkt':\n const wktLoader = loaders.find((loader) => loader.id === 'wkt');\n return wktLoader?.parseTextSync?.(geometry) || null;\n case 'wkb':\n default:\n const wkbLoader = loaders.find((loader) => loader.id === 'wkb');\n const arrayBuffer = ArrayBuffer.isView(geometry)\n ? geometry.buffer.slice(geometry.byteOffset, geometry.byteOffset + geometry.byteLength)\n : geometry;\n const geojson = wkbLoader?.parseSync?.(arrayBuffer, {\n wkb: { shape: 'geojson-geometry' }\n });\n return geojson; // binaryGeometry ? binaryToGeometry(binaryGeometry) : null;\n // const binaryGeometry = WKBLoader.parseSync?.(geometry);\n // ts-ignore\n // return binaryGeometry ? binaryToGeometry(binaryGeometry) : null;\n }\n}\n", "/* eslint-disable indent */\nimport { earcut } from '@math.gl/polygon';\n/**\n * Convert binary features to flat binary arrays. Similar to\n * `geojsonToBinary` helper function, except that it expects\n * a binary representation of the feature data, which enables\n * 2X-3X speed increase in parse speed, compared to using\n * geoJSON. See `binary-vector-tile/VectorTileFeature` for\n * data format detais\n *\n * @param features\n * @param geometryInfo\n * @param options\n * @returns filled arrays\n */\nexport function flatGeojsonToBinary(features, geometryInfo, options) {\n const propArrayTypes = extractNumericPropTypes(features);\n const numericPropKeys = Object.keys(propArrayTypes).filter((k) => propArrayTypes[k] !== Array);\n return fillArrays(features, {\n propArrayTypes,\n ...geometryInfo\n }, {\n numericPropKeys: (options && options.numericPropKeys) || numericPropKeys,\n PositionDataType: options ? options.PositionDataType : Float32Array,\n triangulate: options ? options.triangulate : true\n });\n}\nexport const TEST_EXPORTS = {\n extractNumericPropTypes\n};\n/**\n * Extracts properties that are always numeric\n *\n * @param features\n * @returns object with numeric types\n */\nfunction extractNumericPropTypes(features) {\n const propArrayTypes = {};\n for (const feature of features) {\n if (feature.properties) {\n for (const key in feature.properties) {\n // If property has not been seen before, or if property has been numeric\n // in all previous features, check if numeric in this feature\n // If not numeric, Array is stored to prevent rechecking in the future\n // Additionally, detects if 64 bit precision is required\n const val = feature.properties[key];\n propArrayTypes[key] = deduceArrayType(val, propArrayTypes[key]);\n }\n }\n }\n return propArrayTypes;\n}\n/**\n * Fills coordinates into pre-allocated typed arrays\n *\n * @param features\n * @param geometryInfo\n * @param options\n * @returns an accessor object with value and size keys\n */\n// eslint-disable-next-line complexity, max-statements\nfunction fillArrays(features, geometryInfo, options) {\n const { pointPositionsCount, pointFeaturesCount, linePositionsCount, linePathsCount, lineFeaturesCount, polygonPositionsCount, polygonObjectsCount, polygonRingsCount, polygonFeaturesCount, propArrayTypes, coordLength } = geometryInfo;\n const { numericPropKeys = [], PositionDataType = Float32Array, triangulate = true } = options;\n const hasGlobalId = features[0] && 'id' in features[0];\n const GlobalFeatureIdsDataType = features.length > 65535 ? Uint32Array : Uint16Array;\n const points = {\n type: 'Point',\n positions: new PositionDataType(pointPositionsCount * coordLength),\n globalFeatureIds: new GlobalFeatureIdsDataType(pointPositionsCount),\n featureIds: pointFeaturesCount > 65535\n ? new Uint32Array(pointPositionsCount)\n : new Uint16Array(pointPositionsCount),\n numericProps: {},\n properties: [],\n fields: []\n };\n const lines = {\n type: 'LineString',\n pathIndices: linePositionsCount > 65535\n ? new Uint32Array(linePathsCount + 1)\n : new Uint16Array(linePathsCount + 1),\n positions: new PositionDataType(linePositionsCount * coordLength),\n globalFeatureIds: new GlobalFeatureIdsDataType(linePositionsCount),\n featureIds: lineFeaturesCount > 65535\n ? new Uint32Array(linePositionsCount)\n : new Uint16Array(linePositionsCount),\n numericProps: {},\n properties: [],\n fields: []\n };\n const polygons = {\n type: 'Polygon',\n polygonIndices: polygonPositionsCount > 65535\n ? new Uint32Array(polygonObjectsCount + 1)\n : new Uint16Array(polygonObjectsCount + 1),\n primitivePolygonIndices: polygonPositionsCount > 65535\n ? new Uint32Array(polygonRingsCount + 1)\n : new Uint16Array(polygonRingsCount + 1),\n positions: new PositionDataType(polygonPositionsCount * coordLength),\n globalFeatureIds: new GlobalFeatureIdsDataType(polygonPositionsCount),\n featureIds: polygonFeaturesCount > 65535\n ? new Uint32Array(polygonPositionsCount)\n : new Uint16Array(polygonPositionsCount),\n numericProps: {},\n properties: [],\n fields: []\n };\n if (triangulate) {\n polygons.triangles = [];\n }\n // Instantiate numeric properties arrays; one value per vertex\n for (const object of [points, lines, polygons]) {\n for (const propName of numericPropKeys) {\n // If property has been numeric in all previous features in which the property existed, check\n // if numeric in this feature\n const T = propArrayTypes[propName];\n object.numericProps[propName] = new T(object.positions.length / coordLength);\n }\n }\n // Set last element of path/polygon indices as positions length\n lines.pathIndices[linePathsCount] = linePositionsCount;\n polygons.polygonIndices[polygonObjectsCount] = polygonPositionsCount;\n polygons.primitivePolygonIndices[polygonRingsCount] = polygonPositionsCount;\n const indexMap = {\n pointPosition: 0,\n pointFeature: 0,\n linePosition: 0,\n linePath: 0,\n lineFeature: 0,\n polygonPosition: 0,\n polygonObject: 0,\n polygonRing: 0,\n polygonFeature: 0,\n feature: 0\n };\n for (const feature of features) {\n const geometry = feature.geometry;\n const properties = feature.properties || {};\n switch (geometry.type) {\n case 'Point':\n handlePoint(geometry, points, indexMap, coordLength, properties);\n points.properties.push(keepStringProperties(properties, numericPropKeys));\n if (hasGlobalId) {\n points.fields.push({ id: feature.id });\n }\n indexMap.pointFeature++;\n break;\n case 'LineString':\n handleLineString(geometry, lines, indexMap, coordLength, properties);\n lines.properties.push(keepStringProperties(properties, numericPropKeys));\n if (hasGlobalId) {\n lines.fields.push({ id: feature.id });\n }\n indexMap.lineFeature++;\n break;\n case 'Polygon':\n handlePolygon(geometry, polygons, indexMap, coordLength, properties);\n polygons.properties.push(keepStringProperties(properties, numericPropKeys));\n if (hasGlobalId) {\n polygons.fields.push({ id: feature.id });\n }\n indexMap.polygonFeature++;\n break;\n default:\n throw new Error('Invalid geometry type');\n }\n indexMap.feature++;\n }\n // Wrap each array in an accessor object with value and size keys\n return makeAccessorObjects(points, lines, polygons, coordLength);\n}\n/**\n * Fills (Multi)Point coordinates into points object of arrays\n *\n * @param geometry\n * @param points\n * @param indexMap\n * @param coordLength\n * @param properties\n */\nfunction handlePoint(geometry, points, indexMap, coordLength, properties) {\n points.positions.set(geometry.data, indexMap.pointPosition * coordLength);\n const nPositions = geometry.data.length / coordLength;\n fillNumericProperties(points, properties, indexMap.pointPosition, nPositions);\n points.globalFeatureIds.fill(indexMap.feature, indexMap.pointPosition, indexMap.pointPosition + nPositions);\n points.featureIds.fill(indexMap.pointFeature, indexMap.pointPosition, indexMap.pointPosition + nPositions);\n indexMap.pointPosition += nPositions;\n}\n/**\n * Fills (Multi)LineString coordinates into lines object of arrays\n *\n * @param geometry\n * @param lines\n * @param indexMap\n * @param coordLength\n * @param properties\n */\nfunction handleLineString(geometry, lines, indexMap, coordLength, properties) {\n lines.positions.set(geometry.data, indexMap.linePosition * coordLength);\n const nPositions = geometry.data.length / coordLength;\n fillNumericProperties(lines, properties, indexMap.linePosition, nPositions);\n lines.globalFeatureIds.fill(indexMap.feature, indexMap.linePosition, indexMap.linePosition + nPositions);\n lines.featureIds.fill(indexMap.lineFeature, indexMap.linePosition, indexMap.linePosition + nPositions);\n for (let i = 0, il = geometry.indices.length; i < il; ++i) {\n // Extract range of data we are working with, defined by start\n // and end indices (these index into the geometry.data array)\n const start = geometry.indices[i];\n const end = i === il - 1\n ? geometry.data.length // last line, so read to end of data\n : geometry.indices[i + 1]; // start index for next line\n lines.pathIndices[indexMap.linePath++] = indexMap.linePosition;\n indexMap.linePosition += (end - start) / coordLength;\n }\n}\n/**\n * Fills (Multi)Polygon coordinates into polygons object of arrays\n *\n * @param geometry\n * @param polygons\n * @param indexMap\n * @param coordLength\n * @param properties\n */\nfunction handlePolygon(geometry, polygons, indexMap, coordLength, properties) {\n polygons.positions.set(geometry.data, indexMap.polygonPosition * coordLength);\n const nPositions = geometry.data.length / coordLength;\n fillNumericProperties(polygons, properties, indexMap.polygonPosition, nPositions);\n polygons.globalFeatureIds.fill(indexMap.feature, indexMap.polygonPosition, indexMap.polygonPosition + nPositions);\n polygons.featureIds.fill(indexMap.polygonFeature, indexMap.polygonPosition, indexMap.polygonPosition + nPositions);\n // Unlike Point & LineString geometry.indices is a 2D array\n for (let l = 0, ll = geometry.indices.length; l < ll; ++l) {\n const startPosition = indexMap.polygonPosition;\n polygons.polygonIndices[indexMap.polygonObject++] = startPosition;\n const areas = geometry.areas[l];\n const indices = geometry.indices[l];\n const nextIndices = geometry.indices[l + 1];\n for (let i = 0, il = indices.length; i < il; ++i) {\n const start = indices[i];\n const end = i === il - 1\n ? // last line, so either read to:\n nextIndices === undefined\n ? geometry.data.length // end of data (no next indices)\n : nextIndices[0] // start of first line in nextIndices\n : indices[i + 1]; // start index for next line\n polygons.primitivePolygonIndices[indexMap.polygonRing++] = indexMap.polygonPosition;\n indexMap.polygonPosition += (end - start) / coordLength;\n }\n const endPosition = indexMap.polygonPosition;\n triangulatePolygon(polygons, areas, indices, { startPosition, endPosition, coordLength });\n }\n}\n/**\n * Triangulate polygon using earcut\n *\n * @param polygons\n * @param areas\n * @param indices\n * @param param3\n */\nfunction triangulatePolygon(polygons, areas, indices, { startPosition, endPosition, coordLength }) {\n if (!polygons.triangles) {\n return;\n }\n const start = startPosition * coordLength;\n const end = endPosition * coordLength;\n // Extract positions and holes for just this polygon\n const polygonPositions = polygons.positions.subarray(start, end);\n // Holes are referenced relative to outer polygon\n const offset = indices[0];\n const holes = indices.slice(1).map((n) => (n - offset) / coordLength);\n // Compute triangulation\n const triangles = earcut(polygonPositions, holes, coordLength, areas);\n // Indices returned by triangulation are relative to start\n // of polygon, so we need to offset\n for (let t = 0, tl = triangles.length; t < tl; ++t) {\n polygons.triangles.push(startPosition + triangles[t]);\n }\n}\n/**\n * Wraps an object containing array into accessors\n *\n * @param obj\n * @param size\n */\nfunction wrapProps(obj, size) {\n const returnObj = {};\n for (const key in obj) {\n returnObj[key] = { value: obj[key], size };\n }\n return returnObj;\n}\n/**\n * Wrap each array in an accessor object with value and size keys\n *\n * @param points\n * @param lines\n * @param polygons\n * @param coordLength\n * @returns object\n */\nfunction makeAccessorObjects(points, lines, polygons, coordLength) {\n const binaryFeatures = {\n shape: 'binary-feature-collection',\n points: {\n ...points,\n positions: { value: points.positions, size: coordLength },\n globalFeatureIds: { value: points.globalFeatureIds, size: 1 },\n featureIds: { value: points.featureIds, size: 1 },\n numericProps: wrapProps(points.numericProps, 1)\n },\n lines: {\n ...lines,\n positions: { value: lines.positions, size: coordLength },\n pathIndices: { value: lines.pathIndices, size: 1 },\n globalFeatureIds: { value: lines.globalFeatureIds, size: 1 },\n featureIds: { value: lines.featureIds, size: 1 },\n numericProps: wrapProps(lines.numericProps, 1)\n },\n polygons: {\n ...polygons,\n positions: { value: polygons.positions, size: coordLength },\n polygonIndices: { value: polygons.polygonIndices, size: 1 },\n primitivePolygonIndices: { value: polygons.primitivePolygonIndices, size: 1 },\n globalFeatureIds: { value: polygons.globalFeatureIds, size: 1 },\n featureIds: { value: polygons.featureIds, size: 1 },\n numericProps: wrapProps(polygons.numericProps, 1)\n } // triangles not expected\n };\n if (binaryFeatures.polygons && polygons.triangles) {\n binaryFeatures.polygons.triangles = { value: new Uint32Array(polygons.triangles), size: 1 };\n }\n return binaryFeatures;\n}\n/**\n * Add numeric properties to object\n *\n * @param object\n * @param properties\n * @param index\n * @param length\n */\nfunction fillNumericProperties(object, properties, index, length) {\n for (const numericPropName in object.numericProps) {\n if (numericPropName in properties) {\n const value = properties[numericPropName];\n object.numericProps[numericPropName].fill(value, index, index + length);\n }\n }\n}\n/**\n * Keep string properties in object\n *\n * @param properties\n * @param numericKeys\n * @returns object\n */\nfunction keepStringProperties(properties, numericKeys) {\n const props = {};\n for (const key in properties) {\n if (!numericKeys.includes(key)) {\n props[key] = properties[key];\n }\n }\n return props;\n}\n/**\n *\n * Deduce correct array constructor to use for a given value\n *\n * @param x value to test\n * @param constructor previous constructor deduced\n * @returns PropArrayConstructor\n */\nfunction deduceArrayType(x, constructor) {\n if (constructor === Array || !Number.isFinite(x)) {\n return Array;\n }\n // If this or previous value required 64bits use Float64Array\n return constructor === Float64Array || Math.fround(x) !== x ? Float64Array : Float32Array;\n}\n", "/**\n * Initial scan over GeoJSON features\n * Counts number of coordinates of each geometry type and\n * keeps track of the max coordinate dimensions\n */\n// eslint-disable-next-line complexity, max-statements\nexport function extractGeometryInfo(features) {\n // Counts the number of _positions_, so [x, y, z] counts as one\n let pointPositionsCount = 0;\n let pointFeaturesCount = 0;\n let linePositionsCount = 0;\n let linePathsCount = 0;\n let lineFeaturesCount = 0;\n let polygonPositionsCount = 0;\n let polygonObjectsCount = 0;\n let polygonRingsCount = 0;\n let polygonFeaturesCount = 0;\n const coordLengths = new Set();\n for (const feature of features) {\n const geometry = feature.geometry;\n switch (geometry.type) {\n case 'Point':\n pointFeaturesCount++;\n pointPositionsCount++;\n coordLengths.add(geometry.coordinates.length);\n break;\n case 'MultiPoint':\n pointFeaturesCount++;\n pointPositionsCount += geometry.coordinates.length;\n for (const point of geometry.coordinates) {\n coordLengths.add(point.length);\n }\n break;\n case 'LineString':\n lineFeaturesCount++;\n linePositionsCount += geometry.coordinates.length;\n linePathsCount++;\n for (const coord of geometry.coordinates) {\n coordLengths.add(coord.length);\n }\n break;\n case 'MultiLineString':\n lineFeaturesCount++;\n for (const line of geometry.coordinates) {\n linePositionsCount += line.length;\n linePathsCount++;\n // eslint-disable-next-line max-depth\n for (const coord of line) {\n coordLengths.add(coord.length);\n }\n }\n break;\n case 'Polygon':\n polygonFeaturesCount++;\n polygonObjectsCount++;\n polygonRingsCount += geometry.coordinates.length;\n const flattened = geometry.coordinates.flat();\n polygonPositionsCount += flattened.length;\n for (const coord of flattened) {\n coordLengths.add(coord.length);\n }\n break;\n case 'MultiPolygon':\n polygonFeaturesCount++;\n for (const polygon of geometry.coordinates) {\n polygonObjectsCount++;\n polygonRingsCount += polygon.length;\n const flattened = polygon.flat();\n polygonPositionsCount += flattened.length;\n // eslint-disable-next-line max-depth\n for (const coord of flattened) {\n coordLengths.add(coord.length);\n }\n }\n break;\n default:\n throw new Error(`Unsupported geometry type: ${geometry.type}`);\n }\n }\n return {\n coordLength: coordLengths.size > 0 ? Math.max(...coordLengths) : 2,\n pointPositionsCount,\n pointFeaturesCount,\n linePositionsCount,\n linePathsCount,\n lineFeaturesCount,\n polygonPositionsCount,\n polygonObjectsCount,\n polygonRingsCount,\n polygonFeaturesCount\n };\n}\n", "import { getPolygonSignedArea } from '@math.gl/polygon';\n/**\n * Convert GeoJSON features to Flat GeoJSON features\n *\n * @param features\n * @param options\n * @returns an Array of Flat GeoJSON features\n */\nexport function geojsonToFlatGeojson(features, options = { coordLength: 2, fixRingWinding: true }) {\n return features.map((feature) => flattenFeature(feature, options));\n}\n/**\n * Helper function to copy Point values from `coordinates` into `data` & `indices`\n *\n * @param coordinates\n * @param data\n * @param indices\n * @param options\n */\nfunction flattenPoint(coordinates, data, indices, options) {\n indices.push(data.length);\n data.push(...coordinates);\n // Pad up to coordLength\n for (let i = coordinates.length; i < options.coordLength; i++) {\n data.push(0);\n }\n}\n/**\n * Helper function to copy LineString values from `coordinates` into `data` & `indices`\n *\n * @param coordinates\n * @param data\n * @param indices\n * @param options\n */\nfunction flattenLineString(coordinates, data, indices, options) {\n indices.push(data.length);\n for (const c of coordinates) {\n data.push(...c);\n // Pad up to coordLength\n for (let i = c.length; i < options.coordLength; i++) {\n data.push(0);\n }\n }\n}\n/**\n * Helper function to copy Polygon values from `coordinates` into `data` & `indices` & `areas`\n *\n * @param coordinates\n * @param data\n * @param indices\n * @param areas\n * @param options\n */\nfunction flattenPolygon(coordinates, data, indices, areas, options) {\n let count = 0;\n const ringAreas = [];\n const polygons = [];\n for (const lineString of coordinates) {\n const lineString2d = lineString.map((p) => p.slice(0, 2));\n let area = getPolygonSignedArea(lineString2d.flat());\n const ccw = area < 0;\n // Exterior ring must be CCW and interior rings CW\n if (options.fixRingWinding && ((count === 0 && !ccw) || (count > 0 && ccw))) {\n lineString.reverse();\n area = -area;\n }\n ringAreas.push(area);\n flattenLineString(lineString, data, polygons, options);\n count++;\n }\n if (count > 0) {\n areas.push(ringAreas);\n indices.push(polygons);\n }\n}\n/**\n * Flatten single GeoJSON feature into Flat GeoJSON\n *\n * @param feature\n * @param options\n * @returns A Flat GeoJSON feature\n */\nfunction flattenFeature(feature, options) {\n const { geometry } = feature;\n if (geometry.type === 'GeometryCollection') {\n throw new Error('GeometryCollection type not supported');\n }\n const data = [];\n const indices = [];\n let areas;\n let type;\n switch (geometry.type) {\n case 'Point':\n type = 'Point';\n flattenPoint(geometry.coordinates, data, indices, options);\n break;\n case 'MultiPoint':\n type = 'Point';\n geometry.coordinates.map((c) => flattenPoint(c, data, indices, options));\n break;\n case 'LineString':\n type = 'LineString';\n flattenLineString(geometry.coordinates, data, indices, options);\n break;\n case 'MultiLineString':\n type = 'LineString';\n geometry.coordinates.map((c) => flattenLineString(c, data, indices, options));\n break;\n case 'Polygon':\n type = 'Polygon';\n areas = [];\n flattenPolygon(geometry.coordinates, data, indices, areas, options);\n break;\n case 'MultiPolygon':\n type = 'Polygon';\n areas = [];\n geometry.coordinates.map((c) => flattenPolygon(c, data, indices, areas, options));\n break;\n default:\n throw new Error(`Unknown type: ${type}`);\n }\n return { ...feature, geometry: { type, indices, data, areas } };\n}\n", "import { extractGeometryInfo } from \"./extract-geometry-info.js\";\nimport { geojsonToFlatGeojson } from \"./geojson-to-flat-geojson.js\";\nimport { flatGeojsonToBinary } from \"./flat-geojson-to-binary.js\";\n/**\n * Convert GeoJSON features to flat binary arrays\n *\n * @param features\n * @param options\n * @returns features in binary format, grouped by geometry type\n */\nexport function geojsonToBinary(features, options = { fixRingWinding: true, triangulate: true }) {\n const geometryInfo = extractGeometryInfo(features);\n const coordLength = geometryInfo.coordLength;\n const { fixRingWinding } = options;\n const flatFeatures = geojsonToFlatGeojson(features, { coordLength, fixRingWinding });\n return flatGeojsonToBinary(flatFeatures, geometryInfo, {\n numericPropKeys: options.numericPropKeys,\n PositionDataType: options.PositionDataType || Float32Array,\n triangulate: options.triangulate\n });\n}\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n/**\n * Convert binary geometry representation to GeoJSON\n * @param data geometry data in binary representation\n * @param options\n * @param options.type Input data type: Point, LineString, or Polygon\n * @param options.featureId Global feature id. If specified, only a single feature is extracted\n * @return GeoJSON objects\n */\nexport function binaryToGeojson(data, options) {\n const globalFeatureId = options?.globalFeatureId;\n if (globalFeatureId !== undefined) {\n return getSingleFeature(data, globalFeatureId);\n }\n return parseFeatures(data, options?.type);\n}\n/**\n * Return a single feature from a binary geometry representation as GeoJSON\n * @param data geometry data in binary representation\n * @return GeoJSON feature\n */\nfunction getSingleFeature(data, globalFeatureId) {\n const dataArray = normalizeInput(data);\n for (const data of dataArray) {\n let lastIndex = 0;\n let lastValue = data.featureIds.value[0];\n // Scan through data until we find matching feature\n for (let i = 0; i < data.featureIds.value.length; i++) {\n const currValue = data.featureIds.value[i];\n if (currValue === lastValue) {\n // eslint-disable-next-line no-continue\n continue;\n }\n if (globalFeatureId === data.globalFeatureIds.value[lastIndex]) {\n return parseFeature(data, lastIndex, i);\n }\n lastIndex = i;\n lastValue = currValue;\n }\n if (globalFeatureId === data.globalFeatureIds.value[lastIndex]) {\n return parseFeature(data, lastIndex, data.featureIds.value.length);\n }\n }\n throw new Error(`featureId:${globalFeatureId} not found`);\n}\nfunction parseFeatures(data, type) {\n const dataArray = normalizeInput(data, type);\n return parseFeatureCollection(dataArray);\n}\n/** Parse input binary data and return a valid GeoJSON geometry object */\nexport function binaryToGeometry(data, startIndex, endIndex) {\n switch (data.type) {\n case 'Point':\n return pointToGeoJson(data, startIndex, endIndex);\n case 'LineString':\n return lineStringToGeoJson(data, startIndex, endIndex);\n case 'Polygon':\n return polygonToGeoJson(data, startIndex, endIndex);\n default:\n const unexpectedInput = data;\n throw new Error(`Unsupported geometry type: ${unexpectedInput?.type}`);\n }\n}\n// Normalize features\n// Return an array of data objects, each of which have a type key\nfunction normalizeInput(data, type) {\n const features = [];\n if (data.points) {\n data.points.type = 'Point';\n features.push(data.points);\n }\n if (data.lines) {\n data.lines.type = 'LineString';\n features.push(data.lines);\n }\n if (data.polygons) {\n data.polygons.type = 'Polygon';\n features.push(data.polygons);\n }\n return features;\n}\n/** Parse input binary data and return an array of GeoJSON Features */\nfunction parseFeatureCollection(dataArray) {\n const features = [];\n for (const data of dataArray) {\n if (data.featureIds.value.length === 0) {\n // eslint-disable-next-line no-continue\n continue;\n }\n let lastIndex = 0;\n let lastValue = data.featureIds.value[0];\n // Need to deduce start, end indices of each feature\n for (let i = 0; i < data.featureIds.value.length; i++) {\n const currValue = data.featureIds.value[i];\n if (currValue === lastValue) {\n // eslint-disable-next-line no-continue\n continue;\n }\n features.push(parseFeature(data, lastIndex, i));\n lastIndex = i;\n lastValue = currValue;\n }\n // Last feature\n features.push(parseFeature(data, lastIndex, data.featureIds.value.length));\n }\n return features;\n}\n/** Parse input binary data and return a single GeoJSON Feature */\nfunction parseFeature(data, startIndex, endIndex) {\n const geometry = binaryToGeometry(data, startIndex, endIndex);\n const properties = parseProperties(data, startIndex, endIndex);\n const fields = parseFields(data, startIndex, endIndex);\n return { type: 'Feature', geometry, properties, ...fields };\n}\n/** Parse input binary data and return an object of fields */\nfunction parseFields(data, startIndex = 0, endIndex) {\n return data.fields && data.fields[data.featureIds.value[startIndex]];\n}\n/** Parse input binary data and return an object of properties */\nfunction parseProperties(data, startIndex = 0, endIndex) {\n const properties = Object.assign({}, data.properties[data.featureIds.value[startIndex]]);\n for (const key in data.numericProps) {\n properties[key] = data.numericProps[key].value[startIndex];\n }\n return properties;\n}\n/** Parse binary data of type Polygon */\nfunction polygonToGeoJson(data, startIndex = -Infinity, endIndex = Infinity) {\n const { positions } = data;\n const polygonIndices = data.polygonIndices.value.filter((x) => x >= startIndex && x <= endIndex);\n const primitivePolygonIndices = data.primitivePolygonIndices.value.filter((x) => x >= startIndex && x <= endIndex);\n const multi = polygonIndices.length > 2;\n // Polygon\n if (!multi) {\n const coordinates = [];\n for (let i = 0; i < primitivePolygonIndices.length - 1; i++) {\n const startRingIndex = primitivePolygonIndices[i];\n const endRingIndex = primitivePolygonIndices[i + 1];\n const ringCoordinates = ringToGeoJson(positions, startRingIndex, endRingIndex);\n coordinates.push(ringCoordinates);\n }\n return { type: 'Polygon', coordinates };\n }\n // MultiPolygon\n const coordinates = [];\n for (let i = 0; i < polygonIndices.length - 1; i++) {\n const startPolygonIndex = polygonIndices[i];\n const endPolygonIndex = polygonIndices[i + 1];\n const polygonCoordinates = polygonToGeoJson(data, startPolygonIndex, endPolygonIndex).coordinates;\n coordinates.push(polygonCoordinates);\n }\n return { type: 'MultiPolygon', coordinates };\n}\n/** Parse binary data of type LineString */\nfunction lineStringToGeoJson(data, startIndex = -Infinity, endIndex = Infinity) {\n const { positions } = data;\n const pathIndices = data.pathIndices.value.filter((x) => x >= startIndex && x <= endIndex);\n const multi = pathIndices.length > 2;\n if (!multi) {\n const coordinates = ringToGeoJson(positions, pathIndices[0], pathIndices[1]);\n return { type: 'LineString', coordinates };\n }\n const coordinates = [];\n for (let i = 0; i < pathIndices.length - 1; i++) {\n const ringCoordinates = ringToGeoJson(positions, pathIndices[i], pathIndices[i + 1]);\n coordinates.push(ringCoordinates);\n }\n return { type: 'MultiLineString', coordinates };\n}\n/** Parse binary data of type Point */\nfunction pointToGeoJson(data, startIndex, endIndex) {\n const { positions } = data;\n const coordinates = ringToGeoJson(positions, startIndex, endIndex);\n const multi = coordinates.length > 1;\n if (multi) {\n return { type: 'MultiPoint', coordinates };\n }\n return { type: 'Point', coordinates: coordinates[0] };\n}\n/**\n * Parse a linear ring of positions to a GeoJSON linear ring\n *\n * @param positions Positions TypedArray\n * @param startIndex Start index to include in ring\n * @param endIndex End index to include in ring\n * @returns GeoJSON ring\n */\nfunction ringToGeoJson(positions, startIndex, endIndex) {\n startIndex = startIndex || 0;\n endIndex = endIndex || positions.value.length / positions.size;\n const ringCoordinates = [];\n for (let j = startIndex; j < endIndex; j++) {\n const coord = Array();\n for (let k = j * positions.size; k < (j + 1) * positions.size; k++) {\n coord.push(Number(positions.value[k]));\n }\n ringCoordinates.push(coord);\n }\n return ringCoordinates;\n}\n", "/**\n * Apply transformation to every coordinate of binary features\n * @param binaryFeatures binary features\n * @param transformCoordinate Function to call on each coordinate\n * @return Transformed binary features\n */\nexport function transformBinaryCoords(binaryFeatures, transformCoordinate) {\n if (binaryFeatures.points) {\n transformBinaryGeometryPositions(binaryFeatures.points, transformCoordinate);\n }\n if (binaryFeatures.lines) {\n transformBinaryGeometryPositions(binaryFeatures.lines, transformCoordinate);\n }\n if (binaryFeatures.polygons) {\n transformBinaryGeometryPositions(binaryFeatures.polygons, transformCoordinate);\n }\n return binaryFeatures;\n}\n/** Transform one binary geometry */\nfunction transformBinaryGeometryPositions(binaryGeometry, fn) {\n const { positions } = binaryGeometry;\n for (let i = 0; i < positions.value.length; i += positions.size) {\n // @ts-ignore inclusion of bigint causes problems\n const coord = Array.from(positions.value.subarray(i, i + positions.size));\n const transformedCoord = fn(coord);\n // @ts-ignore typescript typing for .set seems to require bigint?\n positions.value.set(transformedCoord, i);\n }\n}\n/**\n * Apply transformation to every coordinate of GeoJSON features\n *\n * @param features Array of GeoJSON features\n * @param fn Function to call on each coordinate\n * @return Transformed GeoJSON features\n */\nexport function transformGeoJsonCoords(features, fn) {\n for (const feature of features) {\n // @ts-ignore\n feature.geometry.coordinates = coordMap(feature.geometry.coordinates, fn);\n }\n return features;\n}\nfunction coordMap(array, fn) {\n if (isCoord(array)) {\n return fn(array);\n }\n return array.map((item) => {\n return coordMap(item, fn);\n });\n}\nfunction isCoord(array) {\n return Array.isArray(array) && Number.isFinite(array[0]) && Number.isFinite(array[1]);\n}\n"], "mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACSO,IAAM,kCAAkC;AAAA,EAC3C,SAAS;AAAA,EACT,OAAO;AAAA,EACP,aAAa;AAAA,EACb,MAAM;AAAA,EACN,UAAU,CAAC,WAAW,kBAAkB,SAAS;AAAA,EACjD,YAAY;AAAA,IACR,SAAS,EAAE,MAAM,UAAU,OAAO,eAAe;AAAA,IACjD,gBAAgB,EAAE,MAAM,UAAU,WAAW,EAAE;AAAA,IAC/C,SAAS;AAAA,MACL,MAAM;AAAA,MACN,eAAe;AAAA,MACf,mBAAmB;AAAA,QACf,MAAM;AAAA,UACF,MAAM;AAAA,UACN,UAAU,CAAC,YAAY,gBAAgB;AAAA,UACvC,YAAY;AAAA,YACR,UAAU,EAAE,MAAM,UAAU,OAAO,MAAM;AAAA,YACzC,gBAAgB;AAAA,cACZ,MAAM;AAAA,cACN,aAAa;AAAA,cACb,OAAO;AAAA,gBACH,MAAM;AAAA,gBACN,SAAS;AAAA,cACb;AAAA,YACJ;AAAA,YACA,KAAK;AAAA,cACD,OAAO;AAAA,gBACH;AAAA,kBACI,MAAM;AAAA,gBACV;AAAA,gBACA,EAAE,MAAM,OAAO;AAAA,cACnB;AAAA,YACJ;AAAA,YACA,OAAO,EAAE,MAAM,UAAU,MAAM,CAAC,UAAU,WAAW,EAAE;AAAA,YACvD,aAAa,EAAE,MAAM,UAAU,OAAO,mBAAmB;AAAA,YACzD,MAAM;AAAA,cACF,MAAM;AAAA,cACN,OAAO,EAAE,MAAM,SAAS;AAAA,cACxB,OAAO;AAAA,gBACH;AAAA,kBACI,aAAa;AAAA,kBACb,UAAU;AAAA,kBACV,UAAU;AAAA,gBACd;AAAA,gBACA;AAAA,kBACI,aAAa;AAAA,kBACb,UAAU;AAAA,kBACV,UAAU;AAAA,gBACd;AAAA,cACJ;AAAA,YACJ;AAAA,YACA,OAAO,EAAE,MAAM,SAAS;AAAA,UAC5B;AAAA,QACJ;AAAA,MACJ;AAAA,MACA,sBAAsB;AAAA,IAC1B;AAAA,EACJ;AACJ;;;AC/DO,SAAS,eAAe,QAAQ;AACnC,QAAM,cAAc,wBAAwB,QAAQ,KAAK;AACzD,MAAI,CAAC,aAAa;AACd,WAAO;AAAA,EACX;AACA,aAAW,UAAU,OAAO,OAAO,YAAY,WAAW,CAAC,CAAC,GAAG;AAC3D,QAAI,OAAO,UAAU;AACjB,aAAO,WAAW,OAAO,SAAS,YAAY;AAAA,IAClD;AAAA,EACJ;AACA,SAAO;AACX;AAKO,SAAS,eAAe,QAAQ,aAAa;AAChD,QAAM,yBAAyB,KAAK,UAAU,WAAW;AACzD,SAAO,SAAS,MAAM;AAC1B;AAKO,SAAS,kBAAkB,QAAQ;AACtC,QAAM,cAAc,eAAe,MAAM;AACzC,MAAI,CAAC,aAAa;AACd;AAAA,EACJ;AAEA,QAAM,EAAE,SAAS,gBAAgB,QAAQ,IAAI;AAC7C,MAAI,SAAS;AACT,WAAO,SAAS,aAAa,IAAI;AAAA,EACrC;AACA,MAAI,gBAAgB;AAChB,WAAO,SAAS,oBAAoB,IAAI;AAAA,EAC5C;AAEA,SAAO,SAAS,aAAa,IAAI,OAAO,KAAK,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE;AACnE,aAAW,CAAC,YAAY,cAAc,KAAK,OAAO,QAAQ,WAAW,CAAC,CAAC,GAAG;AACtE,UAAM,QAAQ,OAAO,OAAO,KAAK,CAACA,W