import { GeoMap } from './map.js' import MapView from './mapview.js' import { EventHandler } from './utils.js' import { GeoGraphics } from './geographics.js' import { MapList } from './maplist.js' //import { GeoGraphics } from "../pixi/geographics.js" /** * The GeoLayer is a special PIXILayer, that recognizes other GeoLayers and * GeoGraphics. The layer can be adapted to a map and notifies all Geo-Children * of the Adaption. */ export class GeoLayer { constructor(displayObject, opts = {}) { if (displayObject == null || !(displayObject instanceof PIXI.DisplayObject)) { console.error( `You need to provide a displayObject to make a ${this.constructor.name} out of it.`, displayObject ) return null } else { this.geographics = [] // displayObject.map = this this.displayObject = displayObject this.pixiAddChild = displayObject.addChild.bind(displayObject) displayObject.addChild = (...elements) => { elements.forEach(element => { if (element instanceof GeoGraphics) { element.setLayer(this) this.geographics.push(element) this.pixiAddChild(element.graphics) } else { this.pixiAddChild(element) } }) } } this.name = opts['name'] != null ? opts.name : 'Unnamed Layer' this.parent = null this.parentMapLayerTransformedHandler = new EventHandler('onParentMapLayerTransformed') this.layers = [] this._visibility = { min: 0, max: Number.MAX_VALUE } } parentMapLayerTransformed(mapLayer) { this.layers.forEach(layer => { if (!(layer instanceof MapList)) { layer.parentMapLayerTransformed() } }) this.parentMapLayerTransformedHandler.call(null, mapLayer) this.rescaleChildren() } rescaleChildren() { let map = this.map if (this.rescale) { if (map != null) { let scale = map.image.scatter.scale this.displayObject.children.forEach(graphics => { graphics.scale.set(1 / scale, 1 / scale) }) } } let mapLayer = this.mapLayer if (this.visibility && mapLayer != null) { const zoom = mapLayer.mapview.zoom // TODO // Currently I dont know what elemnts was. // We just log an error and resolve this on a later point. if (zoom > this.visibility.min && zoom < this.visibility.max) { this.displayObject.children.forEach(it => (it.visible = true)) } else { this.displayObject.children.forEach(it => (it.visible = false)) } } } set visibility(value) { let { min = 0, max = Infinity } = value console.log(min) this._visibility = { min, max } } get visibility() { return this._visibility } addChild(element) { this.displayObject.addChild(element) } /** * Adapts to a map. If the maplayer should adapt to the parent maplayer, * no parameter must be specified. */ adapt(map = null) { if (!map) map = this.map if (map) { this.geographics.forEach(geographic => { geographic.adaptTo(map) }) this.layers.forEach(layer => { if (layer.adapt) layer.adapt(map) }) this.rescaleChildren() } else console.error('There was no map specified.', this) } // place(geographic) { // if (geographic.constructor.name.startsWith('Geo') && geographic.graphics) { // // Fix to remove the rollupjs circular dependency // //if (geographic instanceof GeoGraphics) { // this.geographics.push(geographic) // super.place(geographic.graphics) // } else super.place(geographic) // } removeFromParent() { if (this.parent) { this.parent.removeLayer(this) } } removeLayer(layer) { let idx = this.layers.indexOf(layer) if (idx != -1) { layer.parent = null this.layers.splice(idx, 1) if (layer.displayObject.parent) { layer.displayObject.parent.removeChild(layer.displayObject) } } else console.warn('Tried to remove layer that was not set.', this, layer) } set parent(parent) { this._parent = parent } get parent() { return this._parent } addLayer(layer) { if (layer instanceof GeoLayer || layer instanceof MapLayer) { layer.removeFromParent() this.layers.push(layer) layer.parent = this layer.parentChanged() this.displayObject.addChild(layer.displayObject) if (this.map) layer.geographics.forEach(geographics => geographics.adaptTo(this.map)) } else console.error('Could not place layer. Only MapLayer and GeoLayers can be child layers of GeoLayers.', layer) } parentChanged() { this.rescaleChildren() } //GeoLayers have to be children of a map layer, // therefore we can recursively get the map. get map() { return this.mapLayer ? this.mapLayer.map : null } get mapLayer() { return this._mapLayer ? this._mapLayer : this.parent.mapLayer } // clone(mapLayerClone) { // const opts = { // mapLayer: mapLayerClone, // map: mapLayerClone.map // } // let geoLayerClone = new GeoLayer(opts) // this.layers.forEach(layer => { // let layerClone = layer.clone(opts) // if (layerClone) { // geoLayerClone.placeLayer(layerClone) // } // }) // this.geographics.forEach(geographics => { // let clone = geographics.clone() // if (clone) { // geoLayerClone.place(clone) // } // }) // return geoLayerClone // } } export class MapLayer extends GeoLayer { constructor( scatterContainer, displayObject, { onTransform = null, onChange = null, focus = null, zoom = null, viewport = null, mapList = null } = {} ) { super(displayObject) this.transformHandler = new EventHandler('onTransform', { listeners: onTransform }) this.scatterContainer = scatterContainer this.changeHandler = new EventHandler('onChange', { listeners: onChange }) this.mapview = new MapView({ zoom, focus, viewport }) this.mapList = mapList this._map = null // //TODO Implement error handling here. // this.maps = maps // if (opts.map) this.placeMap(opts.map) this.dynamicElements = new Map() } adapt() { this.layers.forEach(layer => { if (layer.adapt) layer.adapt(this.map) }) } // placeLayer(layer) { // super.placeLayer(layer) // if (layer instanceof GeoLayer && this.map) { // layer.adapt(this.map) // } // } transformed(e) { this.mapview.transformed(this.map) this.layers.forEach(layer => layer.parentMapLayerTransformed(this)) this.transformHandler.call(this) } clone(container = null) { let clone = {} for (let name of Object.keys(this.maps)) { //console.info(this.maps[name]) clone[name] = this.maps[name].clone(container) } //console.info(this.active) let mapLayerClone = new MapLayer(this.active, clone, container, { name: MapLayer.idx++, viewport: this.mapview.viewport, focus: this.mapview.focus, zoom: this.mapview.zoom }) //mapLayerClone._map = clone['luftbild'] mapLayerClone.childrenVisibility = this.childrenVisibility return mapLayerClone } /** * Changes the map to the specified one, keeping the position and the zoom of the old map. * * @public * @param {GeoMap} map * @memberof MapLayer */ changeMap( map /*, useScatterAsContainer = true // If set to false, the normal container is used. This is necessary when using submaps and the container need to be a RigidContainer.*/ ) { if (map instanceof GeoMap) { console.log('Change map to: ', map) let oldMap = this.map if (oldMap) oldMap.unload() this._map = map this.map.load() this.scatterContainer.addChild(this.map.image) this.mapview.apply(this.map) map.image.addChild(this.displayObject) // A geolayer's displayObject is on the parent layer. // A maplayer's displayobject is always the child of the map. this.adapt() this.changeHandler.call(this, map, oldMap) //Call transform one time manually. this.transformed() this.map.onTransform.add(this.transformed.bind(this)) } else { console.error("Could not set map, it's not of type GeoMap.", map) } /*Logging.log(`Map change: ${key}`) if (this.active !== key) { if (this.maps.hasOwnProperty(key)) { let old = this.map ? this.map : null this._map = this.maps[key] this._map.name = key this.active = key let container = useScatterAsContainer ? this.scatterContainer : this.container this.map.load(container) // Copies all layers. this.layers.forEach(layer => { if (old) this.map.image.addChild(layer.container) }) this.placeMap(this.map) /** * TODO: Improve * * I'm quite sure if I made a design mistake here. * In an earlier version I did not need to migrate the * layers manually from the map to the next map. * * I believe the old version was a container next to the * map, which got updated on transform. * * -SO */ /* if (old) old.unload() } else { let keys = Object.keys(this.maps) if (keys.length == 0) console.error('There is no map set for the map layer!') else { let fallbackMap = keys[0] console.error( `A map with the key (${key}) does not exists within the mapapp. Fallback to map: ${fallbackMap}.` ) this.changeMap(fallbackMap, { useScatterAsContainer }) } } }*/ } get map() { return this._map } /** * This is required for the consistency of georelated layers. * The request traverses up to the mapLayer where it then returns * the responsible map layer. */ get mapLayer() { return this } } MapLayer.idx = 0