@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
152 lines (139 loc) • 5.06 kB
text/typescript
/**
* Creates Mapbox layers.
*
* @remarks
*
* This is best used with the Mapbox camera.
*
* See [sop/mapboxCamera](/docs/nodes/sop/mapboxCamera) for info on how to setup mapbox to use with Polygonjs
*
*/
import {Object3D} from 'three';
import {isBoolean} from '../../../core/Type';
import {FeatureConverter} from '../../../core/thirdParty/Mapbox/FeatureConverter';
import {NodeParamsConfig, ParamConfig} from '../../nodes/utils/params/ParamsConfig';
// import {MapboxListenerParamConfig, MapboxListenerSopNode} from './utils/MapboxListener';
import {pushOnArrayAtEntry} from '../../../core/MapUtils';
import {arrayChunk} from '../../../core/ArrayUtils';
import {TypedSopNode} from './_Base';
import {BaseNodeType} from '../_Base';
import {MapboxMapsController} from '../../../core/thirdParty/Mapbox/MapboxMapsController';
import {ParamType} from '../../poly/ParamType';
// const MULTILINESTRING = 'MultiLineString'
// const LINESTRING = 'LineString'
// const DEFAULT_LIST: Readonly<string> = [
// // 'road-motorway-trunk', // not found in prod, need to investigate
// 'road-primary',
// 'road-secondary-tertiary',
// 'road-street',
// ].join(' ');
// use_bounds: false,
// update_always_allowed: false
class MapboxLayerSopParamsConfig extends NodeParamsConfig {
/** @param names of layers to create */
// layers = ParamConfig.STRING(DEFAULT_LIST);
updateLayers = ParamConfig.BUTTON(null, {
callback: (node: BaseNodeType) => {
MapboxLayerSopNode.PARAM_CALLBACK_reload(node as MapboxLayerSopNode);
},
});
}
const ParamsConfig = new MapboxLayerSopParamsConfig();
export class MapboxLayerSopNode extends TypedSopNode<MapboxLayerSopParamsConfig> {
override paramsConfig = ParamsConfig;
static override type() {
return 'mapboxLayer';
}
override async cook() {
const map = await MapboxMapsController.waitForMap();
if (!map) {
this.states.error.set('map not initialized yet');
return;
}
const layerNames = this.params.spare.filter((param) => param.value == true).map((param) => param.name());
// const layerNames = CoreString.attribNames(this.pv.layers);
const existingLayerNames: string[] = [];
for (const layerName of layerNames) {
if (map.getLayer(layerName)) {
existingLayerNames.push(layerName);
} else {
// const layers = first_map.getStyle().layers;
this.states.error.set(`layer ${layerName} does not exist`);
return;
}
}
const features = map.queryRenderedFeatures(undefined, {
layers: existingLayerNames,
});
const objects: Object3D[] = [];
if (features) {
const featuresByName = this._groupFeaturesByName(features);
featuresByName.forEach((featuresForName, featureName) => {
const converter = new FeatureConverter(this, featureName, featuresForName);
const new_object = converter.createObject();
if (new_object) {
objects.push(new_object);
}
});
}
this.setObjects(objects);
}
private _featuresByName: Map<string, mapboxgl.MapboxGeoJSONFeature[]> = new Map();
private _groupFeaturesByName(
features: mapboxgl.MapboxGeoJSONFeature[]
): Map<string, mapboxgl.MapboxGeoJSONFeature[]> {
this._featuresByName.clear();
for (const feature of features) {
const name = this._feature_name(feature);
if (name) {
pushOnArrayAtEntry(this._featuresByName, name, feature);
}
}
return this._featuresByName;
}
private _feature_name(feature: mapboxgl.MapboxGeoJSONFeature): string | undefined {
const properties = feature['properties'];
let name: string | undefined;
if (properties) {
name = properties['name'] || properties['name_en']; //|| Math.floor(Math.random()*100000000)
if (name == null) {
name = this._id_from_feature(feature);
}
}
return name;
}
private _id_from_feature(feature: mapboxgl.MapboxGeoJSONFeature): string {
const json_str = JSON.stringify(feature.geometry).replace(/{|}|"|:|\[|\]|,|\./g, '');
const json_str_elements = json_str.split('');
const letters_count = 30;
const chunks = arrayChunk(json_str_elements, json_str_elements.length / letters_count);
const first_elements = chunks.map((c) => c[0]);
return first_elements.join('');
}
static PARAM_CALLBACK_reload(node: MapboxLayerSopNode) {
node._paramCallbackReload();
}
private async _paramCallbackReload() {
const map = await MapboxMapsController.waitForMap();
const layers = map.getStyle().layers;
const layerNames = layers.map((layer) => layer.id).sort();
const currentSpareParams = this.params.spare;
const currentValuesByName: Map<string, boolean> = new Map();
for (const spareParam of currentSpareParams) {
const value = spareParam.value;
if (isBoolean(value)) {
currentValuesByName.set(spareParam.name(), value);
}
}
this.params.updateParams({
namesToDelete: currentSpareParams.map((p) => p.name()),
toAdd: layerNames.map((layerName) => ({
name: layerName,
type: ParamType.BOOLEAN,
initValue: currentValuesByName.get(layerName) || false,
rawInput: currentValuesByName.get(layerName) || false,
options: {spare: true},
})),
});
}
}