Skip to content

Commit

Permalink
added(core): The new [ClusterTileLayer](https://heremaps.github.io/xy…
Browse files Browse the repository at this point in the history
…z-maps/docs/classes/core.clustertilelayer.html) enables efficient client-side clustering of map-data from various sources, providing optimized clusters at each zoom level, supporting dynamic feature addition/removal and customizable property aggregation for tailored data summarization and display. [Playground Example](https://heremaps.github.io/xyz-maps/playground/#Layers%20/%20Providers-Cluster_and_display_map-data)

Signed-off-by: Tim Deubler <[email protected]>
  • Loading branch information
TerminalTim committed May 8, 2024
1 parent 9e56423 commit 7cd0859
Show file tree
Hide file tree
Showing 22 changed files with 1,022 additions and 116 deletions.
77 changes: 77 additions & 0 deletions packages/core/src/features/ClusterFeature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2019-2024 HERE Europe B.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
* License-Filename: LICENSE
*/
import {Feature} from './Feature';
import {GeoJSONBBox, GeoJSONCoordinate} from './GeoJSON';

export const updateBBox = (bbox: GeoJSONBBox, [longitude, latitude]: GeoJSONCoordinate) => {
if (longitude < bbox[0]) bbox[0] = longitude;
if (longitude > bbox[2]) bbox[2] = longitude;
if (latitude < bbox[1]) bbox[1] = latitude;
if (latitude > bbox[3]) bbox[3] = latitude;
return bbox;
};
/**
* Properties if a ClusterFeature.
*/
export type ClusterFeatureProperties = {
readonly isCluster?: true;
readonly clusterSize?: number;
readonly zoom?: number;
[name: string]: any;
}

/**
* This Feature represents a Cluster.
*/
export class ClusterFeature extends Feature<'Point'> {
/**
* The Properties of the Cluster feature.
*/
properties: ClusterFeatureProperties;

/**
* Returns the features within the cluster.
* @returns An array containing the features within the cluster.
*/
getFeatures();

getFeatures(result: Feature[]);
getFeatures(result: Feature[] = []) {
for (let feature of this.properties._clustered) {
if (feature instanceof ClusterFeature) {
feature.getFeatures(result);
} else {
result.push(feature);
}
}
return result;
}

/**
* Calculates and returns the bounding box of the cluster.
* @returns The bounding box of the cluster.
*/
getClusterBBox() {
const bbox: GeoJSONBBox = [Infinity, Infinity, -Infinity, -Infinity];
for (let feature of this.getFeatures()) {
updateBBox(bbox, feature.geometry.coordinates);
}
return bbox;
}
}
7 changes: 6 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ export {MVTProvider} from './providers/MVTProvider/MVTProvider';
export {EditableFeatureProvider} from './providers/EditableFeatureProvider';
export {MVTLayerOptions} from './layers/MVTLayerOptions';
export {TileLayerOptions} from './layers/TileLayerOptions';
export {ClusterTileLayer} from './layers/cluster/ClusterTileLayer';
export {ClusterTileLayerOptions} from './layers/cluster/ClusterTileLayerOptions';
export {ClusterFeature, ClusterFeatureProperties} from './features/ClusterFeature';

import webMercatorPrj from './projection/webMercator';

Expand Down Expand Up @@ -116,6 +119,7 @@ import {PixelRect} from './pixel/PixelRect';
import {TileLayer} from './layers/TileLayer';
import {MVTLayer} from './layers/MVTLayer';
import {CustomLayer} from './layers/CustomLayer';
import {ClusterTileLayer} from './layers/cluster/ClusterTileLayer';
import {Feature} from './features/Feature';
import {Tile} from './tile/Tile';
import {tileUtils} from './tile/TileUtils';
Expand Down Expand Up @@ -150,7 +154,8 @@ const pixel = XYZMAPS.pixel = {
const layers = XYZMAPS.layers = {
TileLayer,
MVTLayer,
CustomLayer
CustomLayer,
ClusterTileLayer
};

const features = XYZMAPS.features = {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/layers/Layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ export class Layer {
* @param type
* @param detail
*/
dispatchEvent(type: string, detail: { [name: string]: any, layer?: Layer }) {
dispatchEvent(type: string, detail: { [name: string]: any, layer?: Layer }, async: boolean = false) {
detail.layer = this;
const event = new CustomEvent(type, {detail});
this._l.trigger(type, event, true);
this._l.trigger(type, event, !async);
}
}
2 changes: 1 addition & 1 deletion packages/core/src/layers/MVTLayerOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export interface MVTLayerOptions extends TileLayerOptions {
tileSize?: number
},
/**
* enable or disable pointer-event triggering for all features of all layers.
* Determines whether pointer events are enabled for all features of the layer.
* @defaultValue false
*/
pointerEvents?: boolean
Expand Down
25 changes: 16 additions & 9 deletions packages/core/src/layers/TileLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export const DEFAULT_LAYER_MAX_ZOOM = 32;

let UNDEF;

export function getProviderZoomOffset(tileSize: number) {
return Math.round(Math.log(tileSize) / Math.log(2) - 8);
}

/**
* TileLayer
*/
Expand Down Expand Up @@ -97,7 +101,9 @@ export class TileLayer extends Layer {
const layer = this;
[
ADD_FEATURE_EVENT,
'featuresAdd',
REMOVE_FEATURE_EVENT,
'featuresRemove',
MODIFY_FEATURE_COORDINATES_EVENT,
CLEAR_EVENT,
STYLEGROUP_CHANGE_EVENT,
Expand Down Expand Up @@ -145,18 +151,17 @@ export class TileLayer extends Layer {
this.tileSize = tileSize;
}

this.levelOffset = Math.round(Math.log(this.tileSize) / Math.log(2) - 8);
this.levelOffset = getProviderZoomOffset(this.tileSize);

layer._p.forEach((provider, i) => {
if (provider) {
const proxyEvents = [CLEAR_EVENT];
if (provider.__type == 'FeatureProvider') {
provider.addEventListener(ADD_FEATURE_EVENT, layer._eventProxy, layer);

provider.addEventListener(REMOVE_FEATURE_EVENT, layer._eventProxy, layer);

provider.addEventListener(MODIFY_FEATURE_COORDINATES_EVENT, layer._eventProxy, layer);
proxyEvents.push(ADD_FEATURE_EVENT, 'featuresAdd', REMOVE_FEATURE_EVENT, 'featuresRemove', MODIFY_FEATURE_COORDINATES_EVENT);
}
for (let proxyEvent of proxyEvents) {
provider.addEventListener(proxyEvent, layer._eventProxy, layer);
}
provider.addEventListener(CLEAR_EVENT, layer._eventProxy, layer);
}
});

Expand Down Expand Up @@ -227,10 +232,12 @@ export class TileLayer extends Layer {
detail.layer = this;

if (type == REMOVE_FEATURE_EVENT) {
debugger;
// cleanup styles
// delete this._cs[id];
this.setStyleGroup(detail.feature);
// this.setStyleGroup(detail.feature);
}

this._l.trigger(type, ev, true);
}

Expand Down Expand Up @@ -747,7 +754,7 @@ export class TileLayer extends Layer {


/**
* enable or disable pointer-event triggering for all features of all layers.
* Enable or disable pointer-event triggering for all features of the layer.
*
* @param active - boolean to enable or disable posinter-events.
*
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/layers/TileLayerOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export interface TileLayerOptions {
tileSize?: number;

/**
* enable or disable pointer-event triggering for all features of all layers.
* Determines whether pointer events are enabled for all features of the layer.
* @defaultValue true
*/
pointerEvents?: boolean
Expand Down
Loading

0 comments on commit 7cd0859

Please sign in to comment.