From a471eec55b31fa953e8a2e01c2c98b5bee15e6c0 Mon Sep 17 00:00:00 2001 From: charlieforward9 Date: Mon, 25 Nov 2024 14:44:38 -0500 Subject: [PATCH] feat: multipart support Co-authored-by: Kanahiro Iguchi Co-authored-by: Liam O'flynn --- README.md | 216 ++++++++++++++++++++-------------- example.ts | 312 +++++++++++++++++++++++++++----------------------- package.json | 2 +- src/lines.ts | 66 ++++++----- src/shapes.ts | 125 ++++++++++++-------- 5 files changed, 414 insertions(+), 307 deletions(-) diff --git a/README.md b/README.md index 8a0a551..ed1c0da 100644 --- a/README.md +++ b/README.md @@ -29,19 +29,21 @@ inspired by http://bl.ocks.org/Sumbera/c6fed35c377a46ff74c3 & need. ### Typescript ```ts -import glify from 'leaflet.glify'; +import glify from "leaflet.glify"; // namespace -glify +glify; ``` ### Node + ```js -const { glify } = require('leaflet.glify'); +const { glify } = require("leaflet.glify"); // namespace -glify +glify; ``` ### Simple Points Usage + ```ts L.glify.points({ map, @@ -57,6 +59,7 @@ L.glify.points({ ``` ### Simple Lines Usage + ```ts L.glify.lines({ map: map, @@ -76,6 +79,7 @@ L.glify.lines({ ``` ### Simple Polygon Usage + ```ts L.glify.shapes({ map, @@ -86,119 +90,156 @@ L.glify.shapes({ }, hover: (e, feature): boolean | void => { // do something when a shape is hovered - } + }, }); ``` ## API + **`L.glify` methods** -* [`points(options)`](#lglifypointsoptions-object) -* [`lines(options)`](#lglifylinesoptions-object) -* [`shapes(options)`](#lglifyshapesoptions-object) -* [`longitudeFirst()`](#longitudefirst) -* [`latitudeFirst()`](#latitudefirst) + +- [`points(options)`](#lglifypointsoptions-object) +- [`lines(options)`](#lglifylinesoptions-object) +- [`shapes(options)`](#lglifyshapesoptions-object) +- [`longitudeFirst()`](#longitudefirst) +- [`latitudeFirst()`](#latitudefirst) **`L.glify` properties** -* [`pointsInstances`](#pointsinstances) -* [`linesInstances`](#linesinstances) -* [`shapesInstances`](#shapesinstances) + +- [`pointsInstances`](#pointsinstances) +- [`linesInstances`](#linesinstances) +- [`shapesInstances`](#shapesinstances) --- + ### `L.glify.points(options: object)` + Adds point data passed in `options.data` to the Leaflet map instance passed in `options.map`. + #### Returns + `L.glify.Points` instance + #### Options -* `map` `{Object}` required leaflet map -* `data` `{Object}` required geojson `FeatureCollection` object or an array of `[lat: number, lng: number]` arrays -* `vertexShaderSource` `{String|Function}` optional glsl vertex shader source, defaults to use `L.glify.shader.vertex` -* `fragmentShaderSource` `{String|Function}` optional glsl fragment shader source, defaults to use `L.glify.shader.fragment.point` -* `click` `{Function}` optional event handler for clicking a point -* `hover` `{Function}` optional event handler for hovering a point -* `color` `{Function|Object|String}` optional, default is 'random' - * When `color` is a `Function` its arguments are the `index`:`number` and the `point`:`array` that is being colored, opacity can optionally be included as `{ a: number }`. + +- `map` `{Object}` required leaflet map +- `data` `{Object}` required geojson `FeatureCollection` object or an array of `[lat: number, lng: number]` arrays +- `vertexShaderSource` `{String|Function}` optional glsl vertex shader source, defaults to use `L.glify.shader.vertex` +- `fragmentShaderSource` `{String|Function}` optional glsl fragment shader source, defaults to use `L.glify.shader.fragment.point` +- `click` `{Function}` optional event handler for clicking a point +- `hover` `{Function}` optional event handler for hovering a point +- `color` `{Function|Object|String}` optional, default is 'random' + - When `color` is a `Function` its arguments are the `index`:`number` and the `point`:`array` that is being colored, opacity can optionally be included as `{ a: number }`. The result should be of interface `IColor`, example: `{r: number, g: number, b: number, a: number }`. -* `opacity` `{Number}` a value from 0 to 1, default is 0.8. Only used when opacity isn't included on color. -* `className` `{String}` a class name applied to canvas, default is '' -* `size` `{Number|Function}` pixel size of point - * When `size` is a `Function` its arguments are `index`:`number`, and the `point`:`array` that is being sized -* `sensitivity` `{Number}` exaggerates the size of the clickable area to make it easier to click a point -* `sensitivityHover` `{Number}` exaggerates the size of the hoverable area to make it easier to hover a point -* `preserveDrawingBuffer` `{Boolean}` optional, default `false`, perverse draw buffer on webgl context. - * CAUTION: May cause performance issue with large data sets. -* `pane` `{String}` optional, default is `overlayPane`. Can be set to a custom pane. +- `opacity` `{Number}` a value from 0 to 1, default is 0.8. Only used when opacity isn't included on color. +- `className` `{String}` a class name applied to canvas, default is '' +- `size` `{Number|Function}` pixel size of point + - When `size` is a `Function` its arguments are `index`:`number`, and the `point`:`array` that is being sized +- `sensitivity` `{Number}` exaggerates the size of the clickable area to make it easier to click a point +- `sensitivityHover` `{Number}` exaggerates the size of the hoverable area to make it easier to hover a point +- `preserveDrawingBuffer` `{Boolean}` optional, default `false`, perverse draw buffer on webgl context. + - CAUTION: May cause performance issue with large data sets. +- `pane` `{String}` optional, default is `overlayPane`. Can be set to a custom pane. + --- + ### `L.glify.lines(options: object)` + Adds line data passed in `options.data` to the Leaflet map instance passed in `options.map`. + #### Returns + `L.glify.Lines` instance + #### Options -* `map` `{Object}` required leaflet map -* `data` `{Object}` required geojson `FeatureCollection` object with `geometry.coordinates` arrays being in a `[lat: number, lng: number]` format -* `vertexShaderSource` `{String|Function}` optional glsl vertex shader source, defaults to use `L.glify.shader.vertex` -* `fragmentShaderSource` `{String|Function}` optional glsl fragment shader source, defaults to use `L.glify.shader.fragment.point` -* `click` `{Function}` optional event handler for clicking a line -* `hover` `{Function}` optional event handler for hovering a line -* `hoverOff` `{Function}` optional event handler for hovering off a line -* `color` `{Function|Object|String}` optional, default is 'random' - * When `color` is a `Function` its arguments are the `index`:`number` and the `feature`:`object` that is being colored, opacity can optionally be included as `{ a: number }`. + +- `map` `{Object}` required leaflet map +- `data` `{Object}` required geojson `FeatureCollection` object with `geometry.coordinates` arrays being in a `[lat: number, lng: number]` format +- `vertexShaderSource` `{String|Function}` optional glsl vertex shader source, defaults to use `L.glify.shader.vertex` +- `fragmentShaderSource` `{String|Function}` optional glsl fragment shader source, defaults to use `L.glify.shader.fragment.point` +- `click` `{Function}` optional event handler for clicking a line +- `hover` `{Function}` optional event handler for hovering a line +- `hoverOff` `{Function}` optional event handler for hovering off a line +- `color` `{Function|Object|String}` optional, default is 'random' + - When `color` is a `Function` its arguments are the `index`:`number` and the `feature`:`object` that is being colored, opacity can optionally be included as `{ a: number }`. The result should be of interface `IColor`, example: `{r: number, g: number, b: number, a: number }`. -* `opacity` `{Number}` a value from 0 to 1, default is 0.5. Only used when opacity isn't included on color. -* `className` `{String}` a class name applied to canvas, default is '' -* `sensitivity` `{Number}` exaggerates the size of the clickable area to make it easier to click a line -* `sensitivityHover` `{Number}` exaggerates the size of the hoverable area to make it easier to hover a line -* `preserveDrawingBuffer` `{Boolean}` optional, default `false`, perverse draw buffer on webgl context. - * CAUTION: May cause performance issue with large data sets. -* `weight` `{Number|Function}` a value in pixels of how thick lines should be drawn - * When `weight` is a `Function` its arguments are gets the `index`:`number`, and the `feature`:`object` that is being drawn - * CAUTION: Zoom of more than 18 will turn weight internally to 1 to prevent WebGL precision rendering issues. -* `pane` `{String}` optional, default is `overlayPane`. Can be set to a custom pane. +- `opacity` `{Number}` a value from 0 to 1, default is 0.5. Only used when opacity isn't included on color. +- `className` `{String}` a class name applied to canvas, default is '' +- `sensitivity` `{Number}` exaggerates the size of the clickable area to make it easier to click a line +- `sensitivityHover` `{Number}` exaggerates the size of the hoverable area to make it easier to hover a line +- `preserveDrawingBuffer` `{Boolean}` optional, default `false`, perverse draw buffer on webgl context. + - CAUTION: May cause performance issue with large data sets. +- `weight` `{Number|Function}` a value in pixels of how thick lines should be drawn + - When `weight` is a `Function` its arguments are gets the `index`:`number`, and the `feature`:`object` that is being drawn + - CAUTION: Zoom of more than 18 will turn weight internally to 1 to prevent WebGL precision rendering issues. +- `pane` `{String}` optional, default is `overlayPane`. Can be set to a custom pane. + --- + ### `L.glify.shapes(options: object)` -Adds polygon data passed in `options.data` to the Leaflet map instance passed in `options.map`. + +Adds polygon/multipolygon data passed in `options.data` to the Leaflet map instance passed in `options.map`. + #### Returns + `L.glify.Shapes` instance + #### Options -* `map` `{Object}` required leaflet map -* `data` `{Object}` required geojson `FeatureCollection` object with `geometry.coordinates` arrays being in a `[lng: number, lat: number]` format *Note: `lat` and `lng` are expected in a different order than in `.points()` and `.lines()`* -* `vertexShaderSource` `{String|Function}` optional glsl vertex shader source, defaults to use `L.glify.shader.vertex` -* `fragmentShaderSource` `{String|Function}` optional glsl fragment shader source, defaults to use `L.glify.shader.fragment.polygon` -* `click` `{Function}` optional event handler for clicking a shape -* `hover` `{Function}` optional event handler for hovering a shape -* `color` `{Function|Object|String}` optional, default is 'random' - * When `color` is a `Function` its arguments are the `index`:`number` and the `feature`:`object` that is being colored, opacity can optionally be included as `{ a: number }`. + +- `map` `{Object}` required leaflet map +- `data` `{Object}` required geojson `FeatureCollection` object with `geometry.coordinates` arrays being in a `[lng: number, lat: number]` format _Note: `lat` and `lng` are expected in a different order than in `.points()` and `.lines()`_ +- `vertexShaderSource` `{String|Function}` optional glsl vertex shader source, defaults to use `L.glify.shader.vertex` +- `fragmentShaderSource` `{String|Function}` optional glsl fragment shader source, defaults to use `L.glify.shader.fragment.polygon` +- `click` `{Function}` optional event handler for clicking a shape +- `hover` `{Function}` optional event handler for hovering a shape +- `color` `{Function|Object|String}` optional, default is 'random' + - When `color` is a `Function` its arguments are the `index`:`number` and the `feature`:`object` that is being colored, opacity can optionally be included as `{ a: number }`. The result should be of interface `IColor`, example: `{r: number, g: number, b: number, a: number }`. -* `opacity` `{Number}` a value from 0 to 1, default is 0.5. Only used when opacity isn't included on color. -* `className` `{String}` a class name applied to canvas, default is '' -* `border` `{Boolean}` optional, default `false`. When set to `true`, a border with an opacity of `settings.borderOpacity` is displayed. -* `borderOpacity` `{Number}` optional, default `false`. Border opacity for when `settings.boarder` is `true`. Default is 1. -* `preserveDrawingBuffer` `{Boolean}` optional, default `1`, adjusts the border opacity separate from `opacity`. - * CAUTION: May cause performance issue with large data sets. -* `pane` `{String}` optional, default is `overlayPane`. Can be set to a custom pane. +- `opacity` `{Number}` a value from 0 to 1, default is 0.5. Only used when opacity isn't included on color. +- `className` `{String}` a class name applied to canvas, default is '' +- `border` `{Boolean}` optional, default `false`. When set to `true`, a border with an opacity of `settings.borderOpacity` is displayed. +- `borderOpacity` `{Number}` optional, default `false`. Border opacity for when `settings.boarder` is `true`. Default is 1. +- `preserveDrawingBuffer` `{Boolean}` optional, default `1`, adjusts the border opacity separate from `opacity`. + - CAUTION: May cause performance issue with large data sets. +- `pane` `{String}` optional, default is `overlayPane`. Can be set to a custom pane. + --- + ### `longitudeFirst()` + Sets the expecetd order of arrays in the `coordinates` array of GeoJSON passed to `options.data` to be `[lng, lat]` + #### Returns + The updated `L.glify` instance it was called on --- + ### `latitudeFirst()` + Sets the expecetd order of arrays in the `coordinates` array of GeoJSON passed to `options.data` to be `[lat, lng]` + #### Returns + The updated `L.glify` instance it was called on --- -### `pointsInstances` + +### `pointsInstances` + All of the `L.glify.Points` instances --- + ### `linesInstances` + All of the `L.glify.Lines` instances --- + ### `shapesInstances` -All of the `L.glify.Shapes` instances +All of the `L.glify.Shapes` instances ## Building @@ -213,9 +254,11 @@ Use `yarn serve` or `npm run serve` Use `yarn test` or `npm run test` ## Update & Remove Data + `L.glify` instances can be updated using the `update(data, index)` method. -* `data` `{Object}` Lines and Shapes require a single GeoJSON feature. Points require the same data structure as the original object and therefore also accept an array of coordinates. -* `index` `{number}` An integer indicating the index of the element to be updated. + +- `data` `{Object}` Lines and Shapes require a single GeoJSON feature. Points require the same data structure as the original object and therefore also accept an array of coordinates. +- `index` `{number}` An integer indicating the index of the element to be updated. An object or some elements of an object are removed using the `remove(index)` method. @@ -223,30 +266,33 @@ An object or some elements of an object are removed using the `remove(index)` me - `index` `{number|Array}` optional - An integer or an array of integers specifying the indices of the elements to be removed. If `index` is not defined, the entire object is removed. - + ### Example + ```ts let newPoints = L.glify.points({ map: leafletMap, data: geojsonFeatureCollection, - size: 30 + size: 30, }); // Update the third feature -newPoints.update({ - "type": "FeatureCollection", - "features": [{ - "type": "Feature", - "properties": {}, - "geometry": { - "type": "Point", - "coordinates": [ - 34.072204277521394 - -118.44255208969116 - ] - } - }] -}, 2); +newPoints.update( + { + type: "FeatureCollection", + features: [ + { + type: "Feature", + properties: {}, + geometry: { + type: "Point", + coordinates: [34.072204277521394 - 118.44255208969116], + }, + }, + ], + }, + 2 +); // Now remove it newPoints.remove(2); diff --git a/example.ts b/example.ts index 1255192..c43af56 100644 --- a/example.ts +++ b/example.ts @@ -1,170 +1,190 @@ -import * as L from 'leaflet'; -import { LeafletMouseEvent } from 'leaflet'; -import { FeatureCollection, LineString } from 'geojson'; -import glify from './src/index'; +import * as L from "leaflet"; +import { LeafletMouseEvent } from "leaflet"; +import { Feature, FeatureCollection, LineString, MultiPolygon } from "geojson"; +import glify from "./src/index"; -const map = L.map('map') - .setView([50.00, 14.44], 7); +const map = L.map("map").setView([50.0, 14.44], 7); -L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png') - .addTo(map); +L.tileLayer( + "https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png" +).addTo(map); Promise.all([ - wget('data/86T.json'), - wget('data/CZDistricts.json'), - wget>('data/rivers.json') -]) - .then(([points, districts, rivers]) => { - glify.shapes({ - map: map, - click: (e, feature): void => { - L.popup() - .setLatLng(e.latlng) - .setContent(`You clicked on ${feature.properties.NAZKR_ENG}`) - .openOn(map); + wget("data/86T.json"), + wget("data/CZDistricts.json"), + wget>("data/rivers.json"), + wget>("data/antarctica.geojson"), +]).then(([points, districts, rivers, antarctica]) => { + glify.shapes({ + map: map, + click: (e, feature): void => { + L.popup() + .setLatLng(e.latlng) + .setContent(`You clicked on ${feature.properties.NAZKR_ENG}`) + .openOn(map); - console.log('clicked on Shape', feature, e); - }, - hover: (e: LeafletMouseEvent, feature) => { - console.log('hovered on Shape', feature, e); - }, - data: districts, - border: true, - }); - - glify.lines({ - map, - latitudeKey: 1, - longitudeKey: 0, - weight: 2, - click: (e: LeafletMouseEvent, feature) => { - L.popup() - .setLatLng(e.latlng) - .setContent(`clicked on Line ${feature.properties.name}`) - .openOn(map); + console.log("clicked on Shape", feature, e); + }, + hover: (e: LeafletMouseEvent, feature) => { + console.log("hovered on Shape", feature, e); + }, + data: districts, + border: true, + }); - console.log('clicked on Line', feature, e); - }, - hover: (e: LeafletMouseEvent, feature) => { - console.log('hovered on Line', feature, e); - }, - hoverOff: (e: LeafletMouseEvent, feature) => { - console.log('hovered off Line', feature, e); - }, - data: rivers - }); + glify.lines({ + map, + latitudeKey: 1, + longitudeKey: 0, + weight: 2, + click: (e: LeafletMouseEvent, feature) => { + L.popup() + .setLatLng(e.latlng) + .setContent(`clicked on Line ${feature.properties.name}`) + .openOn(map); - glify.points({ - map: map, - size: function(i) { - return (Math.random() * 17) + 3; - }, - hover: (e: LeafletMouseEvent, feature) => { - console.log('hovered on Point', feature, e); - }, - click: (e: LeafletMouseEvent, feature) => { - //set up a standalone popup (use a popup as a layer) - L.popup() - .setLatLng(feature) - .setContent(`You clicked the point at longitude:${ e.latlng.lng }, latitude:${ e.latlng.lat }`) - .openOn(map); + console.log("clicked on Line", feature, e); + }, + hover: (e: LeafletMouseEvent, feature) => { + console.log("hovered on Line", feature, e); + }, + hoverOff: (e: LeafletMouseEvent, feature) => { + console.log("hovered off Line", feature, e); + }, + data: rivers, + }); - console.log('clicked on Point', feature, e); - }, - data: points - }); + glify.points({ + map: map, + size: function (i) { + return Math.random() * 17 + 3; + }, + hover: (e: LeafletMouseEvent, feature) => { + console.log("hovered on Point", feature, e); + }, + click: (e: LeafletMouseEvent, feature) => { + //set up a standalone popup (use a popup as a layer) + L.popup() + .setLatLng(feature) + .setContent( + `You clicked the point at longitude:${e.latlng.lng}, latitude:${e.latlng.lat}` + ) + .openOn(map); - glify.points({ - map, - size: (i) => { - return 20; - }, - color: () => { - return { - r: 1, - g: 0, - b: 0, - }; - }, - click: (e: LeafletMouseEvent, feature) => { - //set up a standalone popup (use a popup as a layer) - L.popup() - .setLatLng(feature) - .setContent(`You clicked the point at longitude:${e.latlng.lng}, latitude:${e.latlng.lat}`) - .openOn(map); + console.log("clicked on Point", feature, e); + }, + data: points, + }); - console.log('clicked on Point', feature, e); - }, - hover: (e: LeafletMouseEvent, feature) => { - console.log('hovered on Point', feature, e); - }, - data: [[50.10164799,14.5]] - }); + glify.points({ + map, + size: (i) => { + return 20; + }, + color: () => { + return { + r: 1, + g: 0, + b: 0, + }; + }, + click: (e: LeafletMouseEvent, feature) => { + //set up a standalone popup (use a popup as a layer) + L.popup() + .setLatLng(feature) + .setContent( + `You clicked the point at longitude:${e.latlng.lng}, latitude:${e.latlng.lat}` + ) + .openOn(map); - glify.points({ - map, - size: (i) => { - return 20; - }, - color: () => { - return { - r: 0, - g: 0, - b: 1, - }; - }, - hover: (e: LeafletMouseEvent, feature) => { - console.log('hovered on Point', feature, e); - }, - hoverOff: (e: LeafletMouseEvent, feature) => { + console.log("clicked on Point", feature, e); + }, + hover: (e: LeafletMouseEvent, feature) => { + console.log("hovered on Point", feature, e); + }, + data: [[50.10164799, 14.5]], + }); - }, - click: (e, feature) => { - //set up a standalone popup (use a popup as a layer) - L.popup() - .setLatLng(feature.geometry.coordinates) - .setContent('You clicked on:' + feature.properties.name) - .openOn(map); + glify.points({ + map, + size: (i) => { + return 20; + }, + color: () => { + return { + r: 0, + g: 0, + b: 1, + }; + }, + hover: (e: LeafletMouseEvent, feature) => { + console.log("hovered on Point", feature, e); + }, + hoverOff: (e: LeafletMouseEvent, feature) => {}, + click: (e, feature) => { + //set up a standalone popup (use a popup as a layer) + L.popup() + .setLatLng(feature.geometry.coordinates) + .setContent("You clicked on:" + feature.properties.name) + .openOn(map); - console.log('clicked on Point', feature, e); - }, - data: { //geojson - 'type': 'FeatureCollection', - 'features':[ - { - 'type':'Feature', - 'geometry': { - 'type': 'Point', - 'coordinates': [90, 135] - }, - 'properties': { - 'name': 'North Pole', - 'color': 'red' - } + console.log("clicked on Point", feature, e); + }, + data: { + //geojson + type: "FeatureCollection", + features: [ + { + type: "Feature", + geometry: { + type: "Point", + coordinates: [90, 135], }, - { - 'type':'Feature', - 'geometry': { - 'type': 'Point', - 'coordinates': [90, 45] - }, - 'properties': { - 'name': 'South Pole', - 'color': 'blue' - } - } - ], - } - }); + properties: { + name: "North Pole", + color: "red", + }, + }, + { + type: "Feature", + geometry: { + type: "Point", + coordinates: [90, 45], + }, + properties: { + name: "South Pole", + color: "blue", + }, + }, + ], + }, + }); + + glify.shapes({ + map, + data: antarctica, + border: true, + click: (e, feature) => { + L.popup() + .setLatLng(e.latlng) + .setContent(`You clicked on ${feature.properties.name}`) + .openOn(map); + + console.log("clicked on Shape", feature, e); + }, + hover: (e, feature) => { + console.log("hovered on Shape", feature, e); + }, }); +}); function wget(url: string): Promise { return new Promise((resolve, reject) => { const request = new XMLHttpRequest(); - request.open('GET', url, true); + request.open("GET", url, true); request.onload = () => { if (request.status < 200 && request.status > 400) { - return reject(new Error('failure')); + return reject(new Error("failure")); } resolve(JSON.parse(request.responseText) as T); }; diff --git a/package.json b/package.json index 67a7bd8..891d85e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "leaflet.glify", - "version": "3.3.0", + "version": "3.3.1", "description": "web gl renderer plugin for leaflet", "main": "dist/glify.js", "browser": "dist/glify-browser.js", diff --git a/src/lines.ts b/src/lines.ts index b13eec6..ab94fa3 100644 --- a/src/lines.ts +++ b/src/lines.ts @@ -142,6 +142,7 @@ export class Lines extends BaseGlLayer { let weightFn: WeightCallback | null = null; let chosenColor: color.IColor; let featureIndex = 0; + let coordinates: Position[] | Position[][]; if (typeof color === "function") { colorFn = color; @@ -166,20 +167,29 @@ export class Lines extends BaseGlLayer { ? weightFn(featureIndex, feature) : (weight as number); - const featureVertices = new LineFeatureVertices({ - project, - latitudeKey, - longitudeKey, - color: chosenColor, - weight: chosenWeight, - opacity, - mapCenterPixels, - }); - - featureVertices.fillFromCoordinates(feature.geometry.coordinates); - vertices.push(featureVertices); - if (eachVertex) { - eachVertex(featureVertices); + //coorinates Array Structure depends on whether feature is multipart or not. + //Multi: [ [[],[],[]...], [[],[],[]...], [[],[],[]...]... ], Single: [ [[],[],[]...] ] + //Wrap Single Array to treat two types with same method + coordinates = (feature.geometry || feature).coordinates; + if (feature.geometry.type !== "MultiLineString") { + coordinates = [coordinates as Position[]]; + } + + for (const coordinate of coordinates) { + const featureVertices = new LineFeatureVertices({ + project, + latitudeKey, + longitudeKey, + color: chosenColor, + weight: chosenWeight, + opacity, + mapCenterPixels, + }); + featureVertices.fillFromCoordinates(coordinate as Position[]); + vertices.push(featureVertices); + if (eachVertex) { + eachVertex(featureVertices); + } } } @@ -260,7 +270,10 @@ export class Lines extends BaseGlLayer { gl.vertexAttrib1f(aPointSize, pointSize); mapMatrix.setSize(canvas.width, canvas.height).scaleTo(scale); if (zoom > 18) { - mapMatrix.translateTo((-offset.x + mapCenterPixels.x), (-offset.y + mapCenterPixels.y)); + mapMatrix.translateTo( + -offset.x + mapCenterPixels.x, + -offset.y + mapCenterPixels.y + ); // -- attach matrix value to 'mapMatrix' uniform in shader gl.uniformMatrix4fv(matrix, false, mapMatrix.array); @@ -271,8 +284,8 @@ export class Lines extends BaseGlLayer { for (let xOffset = -weight; xOffset <= weight; xOffset += 0.5) { // -- set base matrix to translate canvas pixel coordinates -> webgl coordinates mapMatrix.translateTo( - (-offset.x + mapCenterPixels.x) + xOffset / scale, - (-offset.y + mapCenterPixels.y) + yOffset / scale + -offset.x + mapCenterPixels.x + xOffset / scale, + -offset.y + mapCenterPixels.y + yOffset / scale ); // -- attach matrix value to 'mapMatrix' uniform in shader gl.uniformMatrix4fv(matrix, false, mapMatrix.array); @@ -300,8 +313,8 @@ export class Lines extends BaseGlLayer { ) { // -- set base matrix to translate canvas pixel coordinates -> webgl coordinates mapMatrix.translateTo( - (-offset.x + mapCenterPixels.x) + xOffset / scale, - (-offset.y + mapCenterPixels.y) + yOffset / scale + -offset.x + mapCenterPixels.x + xOffset / scale, + -offset.y + mapCenterPixels.y + yOffset / scale ); // -- attach matrix value to 'mapMatrix' uniform in shader gl.uniformMatrix4fv(this.matrix, false, mapMatrix.array); @@ -325,14 +338,8 @@ export class Lines extends BaseGlLayer { let foundLines: Lines | null = null; instances.forEach((instance: Lines): void => { - const { - latitudeKey, - longitudeKey, - sensitivity, - weight, - scale, - active, - } = instance; + const { latitudeKey, longitudeKey, sensitivity, weight, scale, active } = + instance; if (!active) return; if (instance.map !== map) return; function checkClick( @@ -450,9 +457,8 @@ export class Lines extends BaseGlLayer { if (!instance.active) return; if (map !== instance.map) return; const oldHoveredFeatures = hoveringFeatures; - const newHoveredFeatures: Array< - Feature - > = []; + const newHoveredFeatures: Array> = + []; instance.hoveringFeatures = newHoveredFeatures; // Check if e.latlng is inside the bbox of the features const bounds = geoJSON(data.features).getBounds(); diff --git a/src/shapes.ts b/src/shapes.ts index f8ba06b..e435262 100644 --- a/src/shapes.ts +++ b/src/shapes.ts @@ -205,61 +205,79 @@ export class Shapes extends BaseGlLayer { const alpha = typeof chosenColor.a === "number" ? chosenColor.a : opacity; coordinates = (feature.geometry || feature).coordinates; - if (!Array.isArray(coordinates[0])) { - continue; - } - flat = earcut.flatten(coordinates); - indices = earcut(flat.vertices, flat.holes, flat.dimensions); - dim = coordinates[0][0].length; - const { longitudeKey, latitudeKey } = this; - for (let i = 0, iMax = indices.length; i < iMax; i++) { - index = indices[i]; - if (typeof flat.vertices[0] === "number") { - triangles.push( - flat.vertices[index * dim + longitudeKey], - flat.vertices[index * dim + latitudeKey] - ); - } else { - throw new Error("unhandled polygon"); - } - } - for (let i = 0, iMax = triangles.length; i < iMax; i) { - pixel = map.project(new LatLng(triangles[i++], triangles[i++]), 0); - vertices.push( - pixel.x - mapCenterPixels.x, - pixel.y - mapCenterPixels.y, - chosenColor.r, - chosenColor.g, - chosenColor.b, - alpha - ); + //coordinates Array Structure depends on whether feature is multipart or not. + //Multi: [ [[],[],[]...], [[],[],[]...], [[],[],[]...]... ], Single: [ [[],[],[]...] ] + //Wrap Single Array to treat two types with same method + if (feature.geometry.type !== "MultiPolygon") { + coordinates = [coordinates]; + } + if ( + coordinates.length == 0 || + !Array.isArray(coordinates[0]) || + !Array.isArray(coordinates[0][0]) + ) { + continue; } - if (border) { - const lines = []; - let holeIndex = 0; - for (let i = 1, iMax = flat.vertices.length - 2; i < iMax; i = i + 2) { - // Skip draw between hole and non-hole vertext - if(((i + 1) / 2) !== flat.holes[holeIndex]) { - lines.push(flat.vertices[i], flat.vertices[i - 1]); - lines.push(flat.vertices[i + 2], flat.vertices[i + 1]); + for (let num in coordinates) { + flat = earcut.flatten(coordinates[num]); + indices = earcut(flat.vertices, flat.holes, flat.dimensions); + dim = coordinates[num][0][0].length; + const { longitudeKey, latitudeKey } = this; + for (let i = 0, iMax = indices.length; i < iMax; i++) { + index = indices[i]; + if (typeof flat.vertices[0] === "number") { + triangles.push( + flat.vertices[index * dim + longitudeKey], + flat.vertices[index * dim + latitudeKey] + ); } else { - holeIndex++; + throw new Error("unhandled polygon"); } } - for (let i = 0, iMax = lines.length; i < iMax; i) { - pixel = latLonToPixel(lines[i++], lines[i++]); - vertexLines.push( + for (let i = 0, iMax = triangles.length; i < iMax; i) { + pixel = map.project(new LatLng(triangles[i++], triangles[i++]), 0); + vertices.push( pixel.x - mapCenterPixels.x, pixel.y - mapCenterPixels.y, chosenColor.r, chosenColor.g, chosenColor.b, - borderOpacity + alpha ); } + + if (border) { + const lines = []; + let holeIndex = 0; + for ( + let i = 1, iMax = flat.vertices.length - 2; + i < iMax; + i = i + 2 + ) { + // Skip draw between hole and non-hole vertext + if ((i + 1) / 2 !== flat.holes[holeIndex]) { + lines.push(flat.vertices[i], flat.vertices[i - 1]); + lines.push(flat.vertices[i + 2], flat.vertices[i + 1]); + } else { + holeIndex++; + } + } + + for (let i = 0, iMax = lines.length; i < iMax; i) { + pixel = latLonToPixel(lines[i++], lines[i++]); + vertexLines.push( + pixel.x - mapCenterPixels.x, + pixel.y - mapCenterPixels.y, + chosenColor.r, + chosenColor.g, + chosenColor.b, + borderOpacity + ); + } + } } } @@ -280,12 +298,23 @@ export class Shapes extends BaseGlLayer { if (!this.gl) return this; const { scale, offset, canvas } = e; - const { mapMatrix, gl, vertices, settings, vertexLines, border, mapCenterPixels } = this; + const { + mapMatrix, + gl, + vertices, + settings, + vertexLines, + border, + mapCenterPixels, + } = this; // -- set base matrix to translate canvas pixel coordinates -> webgl coordinates mapMatrix .setSize(canvas.width, canvas.height) .scaleTo(scale) - .translateTo(-offset.x + mapCenterPixels.x, -offset.y + mapCenterPixels.y); + .translateTo( + -offset.x + mapCenterPixels.x, + -offset.y + mapCenterPixels.y + ); gl.clear(gl.COLOR_BUFFER_BIT); gl.viewport(0, 0, canvas.width, canvas.height); @@ -360,7 +389,10 @@ export class Shapes extends BaseGlLayer { } } - hoveringFeatures: Array | Feature> = []; + hoveringFeatures: Array< + | Feature + | Feature + > = []; // hovers all touching Shapes instances static tryHover( e: LeafletMouseEvent, @@ -374,7 +406,10 @@ export class Shapes extends BaseGlLayer { if (instance.map !== map) return; if (!instance.polygonLookup) return; const oldHoveredFeatures = hoveringFeatures; - const newHoveredFeatures: Array | Feature> = []; + const newHoveredFeatures: Array< + | Feature + | Feature + > = []; instance.hoveringFeatures = newHoveredFeatures; const feature = instance.polygonLookup.search(e.latlng.lng, e.latlng.lat);