2019-11-04 10:59:08 +01:00
import { GeoMap } from './map.js'
import { EventHandler } from './utils.js'
2019-11-04 18:20:32 +01:00
import { GeoGraphics } from './geographics.js'
2019-11-13 12:42:06 +01:00
import { MapList } from './maplist.js'
2019-12-09 18:15:28 +01:00
import MapViewport from './mapviewport.js'
import { ScatterContainer } from '../scatter.js'
2019-11-04 10:59:08 +01:00
/ * *
* 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 .
2019-12-11 15:29:59 +01:00
*
* @ export
* @ class GeoLayer
2019-11-04 10:59:08 +01:00
* /
2019-11-04 18:20:32 +01:00
export class GeoLayer {
2019-11-13 12:42:06 +01:00
constructor ( displayObject , opts = { } ) {
2019-11-04 18:20:32 +01:00
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 {
2019-11-05 11:07:36 +01:00
this . geographics = [ ]
2019-11-13 12:42:06 +01:00
// displayObject.map = this
2019-11-04 18:20:32 +01:00
this . displayObject = displayObject
2019-11-05 11:07:36 +01:00
this . pixiAddChild = displayObject . addChild . bind ( displayObject )
2019-11-04 18:20:32 +01:00
displayObject . addChild = ( ... elements ) => {
elements . forEach ( element => {
if ( element instanceof GeoGraphics ) {
2019-11-13 12:42:06 +01:00
element . setLayer ( this )
2019-11-04 18:20:32 +01:00
this . geographics . push ( element )
2019-11-05 11:07:36 +01:00
this . pixiAddChild ( element . graphics )
2019-11-04 18:20:32 +01:00
} else {
2019-11-05 11:07:36 +01:00
this . pixiAddChild ( element )
2019-11-04 18:20:32 +01:00
}
} )
}
}
2019-11-13 12:42:06 +01:00
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
2019-11-04 10:59:08 +01:00
}
2019-12-09 18:15:28 +01:00
/ * *
* Alias for geoLayer . displayObject . addChild .
*
* @ public
* @ param { GeoGraphics | PIXI . DisplayObject } element - Element to add to the displayObject .
* @ memberof GeoLayer
* /
2019-11-05 11:23:18 +01:00
addChild ( element ) {
this . displayObject . addChild ( element )
}
2019-11-04 10:59:08 +01:00
/ * *
* 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 )
} )
2019-11-13 12:42:06 +01:00
this . rescaleChildren ( )
2019-11-04 10:59:08 +01:00
} else console . error ( 'There was no map specified.' , this )
}
2019-11-13 12:42:06 +01:00
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 )
}
2019-11-20 15:59:10 +01:00
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 )
}
}
2019-11-13 12:42:06 +01:00
set parent ( parent ) {
this . _parent = parent
}
get parent ( ) {
return this . _parent
}
2019-12-09 18:15:28 +01:00
/ * *
* Adds a GeoLayer as child to the GeoLayer .
*
* @ public
* @ param { GeoLayer } layer - GeoLayer to add .
* @ memberof GeoLayer
* /
2019-11-04 18:20:32 +01:00
addLayer ( layer ) {
2019-12-09 18:15:28 +01:00
if ( layer instanceof GeoLayer ) {
2019-11-13 12:42:06 +01:00
layer . removeFromParent ( )
2019-11-04 18:20:32 +01:00
this . layers . push ( layer )
2019-11-13 12:42:06 +01:00
layer . parent = this
layer . parentChanged ( )
2019-11-04 18:20:32 +01:00
this . displayObject . addChild ( layer . displayObject )
2019-11-04 10:59:08 +01:00
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 )
}
2019-11-13 12:42:06 +01:00
parentChanged ( ) {
this . rescaleChildren ( )
}
2019-11-04 10:59:08 +01:00
//GeoLayers have to be children of a map layer,
// therefore we can recursively get the map.
get map ( ) {
2019-11-13 12:42:06 +01:00
return this . mapLayer ? this . mapLayer . map : null
2019-11-04 10:59:08 +01:00
}
get mapLayer ( ) {
return this . _mapLayer ? this . _mapLayer : this . parent . mapLayer
}
2019-11-04 18:20:32 +01:00
// 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
// }
}
2019-11-04 10:59:08 +01:00
2019-12-09 18:15:28 +01:00
/ * *
* 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 }
* /
2019-11-04 18:20:32 +01:00
export class MapLayer extends GeoLayer {
constructor (
2019-11-20 15:59:10 +01:00
mapList ,
2019-11-04 18:20:32 +01:00
scatterContainer ,
displayObject ,
2019-12-02 16:51:20 +01:00
{
onTransform = null ,
onChange = null ,
focus = null ,
zoom = null ,
viewport = null ,
name = null ,
mapChangeLocked = false
} = { }
2019-11-04 18:20:32 +01:00
) {
2019-11-25 18:04:11 +01:00
super ( displayObject , {
name
} )
2019-11-04 18:20:32 +01:00
this . transformHandler = new EventHandler ( 'onTransform' , {
2019-11-13 12:42:06 +01:00
listeners : onTransform
2019-11-04 10:59:08 +01:00
} )
this . scatterContainer = scatterContainer
this . changeHandler = new EventHandler ( 'onChange' , {
2019-11-04 18:20:32 +01:00
listeners : onChange
2019-11-04 10:59:08 +01:00
} )
2019-12-11 15:29:59 +01:00
this . _mapview = new MapViewport ( {
2019-11-04 18:20:32 +01:00
zoom ,
focus ,
viewport
} )
2019-11-13 12:42:06 +01:00
this . mapList = mapList
2019-11-04 18:20:32 +01:00
// //TODO Implement error handling here.
// this.maps = maps
// if (opts.map) this.placeMap(opts.map)
2019-11-04 10:59:08 +01:00
this . dynamicElements = new Map ( )
2019-12-02 16:51:20 +01:00
this . _mapChangeLocked = mapChangeLocked
2019-11-20 15:59:10 +01:00
// Binds the transformed callback beforehand.
this . transformed = this . transformed . bind ( this )
this . changeMap ( mapList . active )
2019-11-04 10:59:08 +01:00
}
2019-12-11 15:29:59 +01:00
//Todo: rename to mapviewport.
/ * *
* Returns the MapViewport of this map layer .
*
* @ readonly
* @ member { MapViewport }
* @ memberof MapLayer
* /
get mapview ( ) {
return this . _mapview
}
2019-12-02 16:51:20 +01:00
get mapChangeLocked ( ) {
return this . _mapChangeLocked
}
lockMapChange ( ) {
this . _mapChangeLocked = true
}
unlockMapChange ( ) {
this . _mapChangeLocked = false
}
2019-12-09 18:15:28 +01:00
/ * *
* Adapts all child layers and their GeoGraphics .
*
* This is called primarily on a map change .
*
* @ private
* @ memberof MapLayer
* /
2019-11-04 10:59:08 +01:00
adapt ( ) {
this . layers . forEach ( layer => {
if ( layer . adapt ) layer . adapt ( this . map )
} )
}
transformed ( e ) {
2019-12-11 15:29:59 +01:00
this . mapview . update ( this . map )
2019-11-13 12:42:06 +01:00
this . layers . forEach ( layer => layer . parentMapLayerTransformed ( this ) )
2019-11-04 10:59:08 +01:00
this . transformHandler . call ( this )
}
2019-12-09 18:15:28 +01:00
/ * *
* 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 .
2019-12-11 15:29:59 +01:00
* @ returns { MapLayer } - Returns the cloned MapLayer .
2019-12-09 18:15:28 +01:00
* @ memberof MapLayer
* /
2019-11-20 15:59:10 +01:00
clone ( scatterContainer , container = null ) {
let mapList = this . mapList . clone ( )
container = container == null ? new PIXI . Container ( ) : container
2019-11-04 10:59:08 +01:00
2019-11-20 15:59:10 +01:00
let mapLayerClone = new MapLayer ( mapList , scatterContainer , container , {
2019-11-04 10:59:08 +01:00
name : MapLayer . idx ++ ,
viewport : this . mapview . viewport ,
focus : this . mapview . focus ,
2019-11-20 15:59:10 +01:00
zoom : this . mapview . zoom ,
mapList
2019-11-04 10:59:08 +01:00
} )
2019-11-20 15:59:10 +01:00
2019-11-04 10:59:08 +01:00
mapLayerClone . childrenVisibility = this . childrenVisibility
return mapLayerClone
}
2019-11-25 18:04:11 +01:00
/ * *
* 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 )
}
2019-11-05 11:07:36 +01:00
/ * *
* Changes the map to the specified one , keeping the position and the zoom of the old map .
*
* @ public
* @ param { GeoMap } map
* @ memberof MapLayer
* /
2019-11-04 10:59:08 +01:00
changeMap (
2019-11-20 15:59:10 +01:00
name
/ * m a p ,
2019-11-05 11:07:36 +01:00
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.*/
2019-11-04 10:59:08 +01:00
) {
2019-12-02 16:51:20 +01:00
if ( ! this . mapChangeLocked ) {
console . log ( '🗺️ Change map to: ' , name )
let oldMap = this . map
2019-11-20 15:59:10 +01:00
2019-12-02 16:51:20 +01:00
this . mapList . select ( name )
2019-11-20 15:59:10 +01:00
2019-12-02 16:51:20 +01:00
if ( oldMap ) {
oldMap . unload ( )
oldMap . onTransform . remove ( this . transformed )
}
2019-11-04 18:20:32 +01:00
2019-12-02 16:51:20 +01:00
let map = this . map
if ( map ) {
map . load ( )
2019-11-04 10:59:08 +01:00
2019-12-02 16:51:20 +01:00
this . scatterContainer . addChild ( map . image )
2019-11-04 10:59:08 +01:00
2019-12-02 16:51:20 +01:00
this . mapview . apply ( map )
map . image . addChild ( this . displayObject )
2019-11-04 10:59:08 +01:00
2019-12-02 16:51:20 +01:00
// A geolayer's displayObject is on the parent layer.
// A maplayer's displayobject is always the child of the map.
this . adapt ( )
2019-11-04 10:59:08 +01:00
2019-12-02 16:51:20 +01:00
this . changeHandler . call ( this , map , oldMap )
2019-11-04 10:59:08 +01:00
2019-12-02 16:51:20 +01:00
//Call transform one time manually.
this . transformed ( )
map . onTransform . add ( this . transformed )
2019-11-04 10:59:08 +01:00
} else {
2019-12-02 16:51:20 +01:00
console . error ( ` Could not change map to ${ name } . ` )
2019-11-04 10:59:08 +01:00
}
2019-12-02 16:51:20 +01:00
}
2019-11-04 10:59:08 +01:00
}
2019-12-09 18:15:28 +01:00
/ * *
* Applies the mapviews focus to the map .
* This may be useful , if the container was modified .
*
* @ memberof MapLayer
* /
2019-11-28 13:36:00 +01:00
refocus ( ) {
this . mapview . apply ( this . map )
}
2019-12-09 18:15:28 +01:00
/ * *
* @ public
* @ returns { GeoMap } - Returns the active map .
* @ readonly
* @ memberof MapLayer
* /
2019-11-04 10:59:08 +01:00
get map ( ) {
2019-11-20 15:59:10 +01:00
return this . mapList . map
2019-11-04 10:59:08 +01:00
}
/ * *
2019-12-09 18:15:28 +01:00
*
* 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
2019-11-04 10:59:08 +01:00
* /
get mapLayer ( ) {
return this
}
2019-11-25 18:04:11 +01:00
2019-12-09 18:15:28 +01:00
/ * *
* Cleans up the MapLayer .
*
* @ public
* @ memberof MapLayer
* /
2019-11-25 18:04:11 +01:00
cleanup ( ) {
this . mapList . cleanup ( )
}
2019-11-04 10:59:08 +01:00
}
MapLayer . idx = 0