462 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			462 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import { GeoMap } from './map.js'
 | 
						|
import { EventHandler } from './utils.js'
 | 
						|
import { GeoGraphics } from './geographics.js'
 | 
						|
import { MapList } from './maplist.js'
 | 
						|
import MapViewport from './mapviewport.js'
 | 
						|
import { ScatterContainer } from '../scatter.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
 | 
						|
 */
 | 
						|
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
 | 
						|
        this._visibility = { min, max }
 | 
						|
    }
 | 
						|
 | 
						|
    get visibility() {
 | 
						|
        return this._visibility
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Alias for geoLayer.displayObject.addChild.
 | 
						|
     *
 | 
						|
     * @public
 | 
						|
     * @param {GeoGraphics | PIXI.DisplayObject} element - Element to add to the displayObject.
 | 
						|
     * @memberof GeoLayer
 | 
						|
     */
 | 
						|
    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)
 | 
						|
    }
 | 
						|
 | 
						|
    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)
 | 
						|
    }
 | 
						|
 | 
						|
    remove(graphics) {
 | 
						|
        if (graphics instanceof GeoGraphics) {
 | 
						|
            let index = this.geographics.indexOf(geographics)
 | 
						|
            if (index != -1) {
 | 
						|
                this.displayObject.removeChild(geographics)
 | 
						|
            } else {
 | 
						|
                console.error('Could not remove geographics from geolayer.', this, geographics)
 | 
						|
            }
 | 
						|
        } else {
 | 
						|
            this.displayObject.removeChild(graphics)
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    set parent(parent) {
 | 
						|
        this._parent = parent
 | 
						|
    }
 | 
						|
 | 
						|
    get parent() {
 | 
						|
        return this._parent
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Adds a GeoLayer as child to the GeoLayer.
 | 
						|
     *
 | 
						|
     * @public
 | 
						|
     * @param {GeoLayer} layer - GeoLayer to add.
 | 
						|
     * @memberof GeoLayer
 | 
						|
     */
 | 
						|
    addLayer(layer) {
 | 
						|
        if (layer instanceof GeoLayer) {
 | 
						|
            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
 | 
						|
    // }
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * The map layer is responsible for showing certain maps, at a specific position It contains
 | 
						|
 * a list of available maps and can switch between them seamlessly. GeoGraphics placed on the MapLayer itself
 | 
						|
 * or child Geolayers will be adapted to maps and adjusted on map change automatically.
 | 
						|
 *
 | 
						|
 * The map layer is the 'king' of the geo layers. Every geolayer
 | 
						|
 * needs a map layer at it's root. Otherwise they won't work-
 | 
						|
 *
 | 
						|
 * @export
 | 
						|
 * @class MapLayer
 | 
						|
 * @extends {GeoLayer}
 | 
						|
 */
 | 
						|
export class MapLayer extends GeoLayer {
 | 
						|
    constructor(
 | 
						|
        mapList,
 | 
						|
        scatterContainer,
 | 
						|
        displayObject,
 | 
						|
        {
 | 
						|
            onTransform = null,
 | 
						|
            onChange = null,
 | 
						|
            focus = null,
 | 
						|
            zoom = null,
 | 
						|
            viewport = null,
 | 
						|
            name = null,
 | 
						|
            mapChangeLocked = false
 | 
						|
        } = {}
 | 
						|
    ) {
 | 
						|
        super(displayObject, {
 | 
						|
            name
 | 
						|
        })
 | 
						|
 | 
						|
        this.transformHandler = new EventHandler('onTransform', {
 | 
						|
            listeners: onTransform
 | 
						|
        })
 | 
						|
 | 
						|
        this.scatterContainer = scatterContainer
 | 
						|
 | 
						|
        this.changeHandler = new EventHandler('onChange', {
 | 
						|
            listeners: onChange
 | 
						|
        })
 | 
						|
 | 
						|
        this._mapview = new MapViewport({
 | 
						|
            zoom,
 | 
						|
            focus,
 | 
						|
            viewport
 | 
						|
        })
 | 
						|
 | 
						|
        this.mapList = mapList
 | 
						|
 | 
						|
        // //TODO Implement error handling here.
 | 
						|
        // this.maps = maps
 | 
						|
        // if (opts.map) this.placeMap(opts.map)
 | 
						|
        this.dynamicElements = new Map()
 | 
						|
        this._mapChangeLocked = mapChangeLocked
 | 
						|
 | 
						|
        // Binds the transformed callback beforehand.
 | 
						|
        this.transformed = this.transformed.bind(this)
 | 
						|
 | 
						|
        this.changeMap(mapList.active)
 | 
						|
    }
 | 
						|
 | 
						|
    //Todo: rename to mapviewport.
 | 
						|
    /**
 | 
						|
     * Returns the MapViewport of this map layer.
 | 
						|
     *
 | 
						|
     * @readonly
 | 
						|
     * @member {MapViewport}
 | 
						|
     * @memberof MapLayer
 | 
						|
     */
 | 
						|
    get mapview() {
 | 
						|
        return this._mapview
 | 
						|
    }
 | 
						|
 | 
						|
    get mapChangeLocked() {
 | 
						|
        return this._mapChangeLocked
 | 
						|
    }
 | 
						|
 | 
						|
    lockMapChange() {
 | 
						|
        this._mapChangeLocked = true
 | 
						|
    }
 | 
						|
 | 
						|
    unlockMapChange() {
 | 
						|
        this._mapChangeLocked = false
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Adapts all child layers and their GeoGraphics.
 | 
						|
     *
 | 
						|
     * This is called primarily on a map change.
 | 
						|
     *
 | 
						|
     * @private
 | 
						|
     * @memberof MapLayer
 | 
						|
     */
 | 
						|
    adapt() {
 | 
						|
        this.layers.forEach(layer => {
 | 
						|
            if (layer.adapt) layer.adapt(this.map)
 | 
						|
        })
 | 
						|
    }
 | 
						|
 | 
						|
    transformed(e) {
 | 
						|
        this.mapview.update(this.map)
 | 
						|
        this.layers.forEach(layer => layer.parentMapLayerTransformed(this))
 | 
						|
        this.transformHandler.call(this)
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Clones the map layer-
 | 
						|
     *
 | 
						|
     * @param {ScatterContainer} scatterContainer - ScatterContainer of the app.
 | 
						|
     * @param {PIXI.DisplayObject} [container=null] - Container of the newly created MapLayer. If null, an empty PIXI.Container will be created.
 | 
						|
     * @returns {MapLayer} - Returns the cloned MapLayer.
 | 
						|
     * @memberof MapLayer
 | 
						|
     */
 | 
						|
    clone(scatterContainer, container = null) {
 | 
						|
        let mapList = this.mapList.clone()
 | 
						|
        container = container == null ? new PIXI.Container() : container
 | 
						|
 | 
						|
        let mapLayerClone = new MapLayer(mapList, scatterContainer, container, {
 | 
						|
            name: MapLayer.idx++,
 | 
						|
            viewport: this.mapview.viewport,
 | 
						|
            focus: this.mapview.focus,
 | 
						|
            zoom: this.mapview.zoom,
 | 
						|
            mapList
 | 
						|
        })
 | 
						|
 | 
						|
        mapLayerClone.childrenVisibility = this.childrenVisibility
 | 
						|
        return mapLayerClone
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Helper function to quickly display the next map.
 | 
						|
     * Order is defined by the key ordering of the maplist.
 | 
						|
     *
 | 
						|
     * @memberof MapLayer
 | 
						|
     */
 | 
						|
    next() {
 | 
						|
        let nextMap = this.mapList.next()
 | 
						|
        this.changeMap(nextMap)
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Changes the map to the specified one, keeping the position and the zoom of the old map.
 | 
						|
     *
 | 
						|
     * @public
 | 
						|
     * @param {GeoMap} map
 | 
						|
     * @memberof MapLayer
 | 
						|
     */
 | 
						|
    changeMap(
 | 
						|
        name
 | 
						|
        /* 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 (!this.mapChangeLocked) {
 | 
						|
            console.log('🗺️ Change map to: ', name)
 | 
						|
            let oldMap = this.map
 | 
						|
 | 
						|
            this.mapList.select(name)
 | 
						|
 | 
						|
            if (oldMap) {
 | 
						|
                oldMap.unload()
 | 
						|
                oldMap.onTransform.remove(this.transformed)
 | 
						|
            }
 | 
						|
 | 
						|
            let map = this.map
 | 
						|
            if (map) {
 | 
						|
                map.load()
 | 
						|
 | 
						|
                this.scatterContainer.addChild(map.image)
 | 
						|
 | 
						|
                this.mapview.apply(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()
 | 
						|
                map.onTransform.add(this.transformed)
 | 
						|
            } else {
 | 
						|
                console.error(`Could not change map to ${name}.`)
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Applies the mapviews focus to the map.
 | 
						|
     * This may be useful, if the container was modified.
 | 
						|
     *
 | 
						|
     * @memberof MapLayer
 | 
						|
     */
 | 
						|
    refocus() {
 | 
						|
        this.mapview.apply(this.map)
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @public
 | 
						|
     * @returns {GeoMap} - Returns the active map.
 | 
						|
     * @readonly
 | 
						|
     * @memberof MapLayer
 | 
						|
     */
 | 
						|
    get map() {
 | 
						|
        return this.mapList.map
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     *
 | 
						|
     * This is required for the geo layers.
 | 
						|
     * MapLayer requests from the geoLayers traverse up to the next MapLayer.
 | 
						|
     *
 | 
						|
     * @public
 | 
						|
     * @returns {MapLayer} - Returns this MapLayer.
 | 
						|
     * @readonly
 | 
						|
     * @memberof MapLayer
 | 
						|
     */
 | 
						|
    get mapLayer() {
 | 
						|
        return this
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Cleans up the MapLayer.
 | 
						|
     *
 | 
						|
     * @public
 | 
						|
     * @memberof MapLayer
 | 
						|
     */
 | 
						|
    cleanup() {
 | 
						|
        this.mapList.cleanup()
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
MapLayer.idx = 0
 |