937 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			937 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
import { MapObjectScatter } from './scatter.js'
 | 
						|
import { DeepZoomImage, DeepZoomInfo } from '../deepzoom/image.js'
 | 
						|
import { MapProjection, DeepZoomMapProjection } from './mapprojection.js'
 | 
						|
import { Points } from '../../utils.js'
 | 
						|
import { EventHandler } from './utils.js'
 | 
						|
import Mercator from './projections/mercator.js'
 | 
						|
 | 
						|
/**
 | 
						|
*   The GeoMap class displays a map, that it gets from MapProjection object.
 | 
						|
*   It handles the current location on the map, the zoom factor, interactions and
 | 
						|
*   the viewport, the area of the map, the user can see and navigate to.
 | 
						|
*
 | 
						|
*   You should not instantiate GeoMap on it's own. Use the implemented child classes
 | 
						|
*   or derive a new one from it.
 | 
						|
*
 | 
						|
*   Note: The name Map is already in use. Therefore GeoMap was used instead.
 | 
						|
*
 | 
						|
 | 
						|
*   @abstract
 | 
						|
*	@class
 | 
						|
* 	@see {@link maps.html}
 | 
						|
*/
 | 
						|
 | 
						|
export class GeoMap {
 | 
						|
    /**
 | 
						|
     * Creates instance of GeoMap
 | 
						|
     *
 | 
						|
     * @constructor
 | 
						|
     * @param {MapProjection}[mapProjection={}] - The map projection describes how the map has to be interpreted by the Map class. E.g. what are the boundaries of the map?
 | 
						|
     * @param {object}[opts={}] - With the opts, the created MapObjectScatter can be adjusted.
 | 
						|
     * @param {boolean}[opts.cover=false] - Enables covering behaviour of a map object. Normally maps should cover the whole app.
 | 
						|
     */
 | 
						|
    constructor(
 | 
						|
        mapProjection = {},
 | 
						|
        {
 | 
						|
            debug = true,
 | 
						|
            cover = true,
 | 
						|
            // Scatter Options
 | 
						|
            alpha = 1,
 | 
						|
            startScale = 1,
 | 
						|
            minScale = 0,
 | 
						|
            maxScale = Infinity,
 | 
						|
            translatable = true,
 | 
						|
            scalable = true,
 | 
						|
            rotatable = false, // Many functionalities are not supported when rotating the map. Mainly the cover mechanism.
 | 
						|
            viewport = mapProjection.maxViewport,
 | 
						|
            // Events
 | 
						|
            onLoad = null,
 | 
						|
            onTransform = null
 | 
						|
        } = {}
 | 
						|
    ) {
 | 
						|
        this._id = GeoMap.counter++
 | 
						|
        this.onLoad = new EventHandler('loaded', { listeners: onLoad })
 | 
						|
        this.onTransform = new EventHandler('transform', { listeners: onTransform })
 | 
						|
 | 
						|
        this._alpha = alpha
 | 
						|
        this._cover = cover
 | 
						|
        this.debug = debug
 | 
						|
 | 
						|
        //TODO discuss if this is required here.
 | 
						|
        // Those are just scatter options and the information
 | 
						|
        // is redundant in the map class and the scatter.
 | 
						|
        this.startScale = startScale
 | 
						|
        this.minScale = minScale
 | 
						|
        this.maxScale = maxScale
 | 
						|
        this.rotatable = rotatable
 | 
						|
        this.translatable = translatable
 | 
						|
        this.scalable = scalable
 | 
						|
        this.viewport = viewport
 | 
						|
 | 
						|
        this._mapProjection = mapProjection
 | 
						|
        this.overlays = {}
 | 
						|
 | 
						|
        /**
 | 
						|
         * Adjust the viewport depending on the mapProjection clipping.
 | 
						|
         */
 | 
						|
        if (this.mapProjection.clip) {
 | 
						|
            const vp = this.viewport
 | 
						|
            const cp = this.mapProjection.clip
 | 
						|
            let bounds = {
 | 
						|
                min: {
 | 
						|
                    x: vp.min.x > cp.min.x ? vp.min.x : cp.min.x,
 | 
						|
                    y: vp.min.y > cp.min.y ? vp.min.y : cp.min.y
 | 
						|
                },
 | 
						|
                max: {
 | 
						|
                    x: vp.max.x < cp.max.x ? vp.max.x : cp.max.x,
 | 
						|
                    y: vp.max.y < cp.max.y ? vp.max.y : cp.max.y
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            this.viewport = bounds
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Determines if the scatter covers the container.
 | 
						|
     * @member {boolean}
 | 
						|
     * @readonly
 | 
						|
     * @memberof GeoMap
 | 
						|
     */
 | 
						|
    get cover() {
 | 
						|
        // For cloning we want to store the cover inside the map.
 | 
						|
        // But the scatter is responsible for the cover state.
 | 
						|
        // So we update the app's cover state every time
 | 
						|
        // we get the value.
 | 
						|
        if (this.image && this.image.scatter && this.image.scatter.cover != null) {
 | 
						|
            this._cover = this.image.scatter.cover
 | 
						|
        }
 | 
						|
 | 
						|
        return this._cover
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the image object used by the GeoMap.
 | 
						|
     *
 | 
						|
     * @readonly
 | 
						|
     * @memberof GeoMap
 | 
						|
     */
 | 
						|
    get image() {
 | 
						|
        return this._image
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * The mapProjection of the map.
 | 
						|
     *
 | 
						|
     * @member {MapProjection}
 | 
						|
     * @readonly
 | 
						|
     * @memberof GeoMap
 | 
						|
     */
 | 
						|
    get mapProjection() {
 | 
						|
        return this._mapProjection
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Clears all EventHandlers.
 | 
						|
     *
 | 
						|
     * @memberof GeoMap
 | 
						|
     */
 | 
						|
    flushHandlers() {
 | 
						|
        this.onLoad.empty()
 | 
						|
        this.onTransform.empty()
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Locks all transformations on the map.
 | 
						|
     * Single parameters can be set if necessary. False means the value is locked, true means they can be modified.
 | 
						|
     *
 | 
						|
     * @public
 | 
						|
     * @param {object} [{ rotatable = false, translatable = false, movableX = false, movableY = false, scalable = false }={}]
 | 
						|
     * @memberof GeoMap
 | 
						|
     */
 | 
						|
    lock({ rotatable = false, translatable = false, movableX = false, movableY = false, scalable = false } = {}) {
 | 
						|
        if (this.image && this.image.scatter) {
 | 
						|
            this.image.scatter.translatable = translatable
 | 
						|
            this.image.scatter.movableX = movableX
 | 
						|
            this.image.scatter.movableY = movableY
 | 
						|
            this.image.scatter.rotatable = rotatable
 | 
						|
            this.image.scatter.scalable = scalable
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Unlocks all transformations on the map.
 | 
						|
     * Single parameters can be set if necessary. False means the value is locked, true means they can be modified.
 | 
						|
     *
 | 
						|
     * @public
 | 
						|
     * @param {object} [{ rotatable = false, translatable = false, movableX = false, movableY = false, scalable = false }={}]
 | 
						|
     * @memberof GeoMap
 | 
						|
     */
 | 
						|
    unlock({ rotatable = true, translatable = true, movableX = true, movableY = true, scalable = true } = {}) {
 | 
						|
        this.lock({ rotatable, translatable, movableX, movableY, scalable })
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Unloads the image of the map.
 | 
						|
     *
 | 
						|
     * @public
 | 
						|
     * @memberof GeoMap
 | 
						|
     */
 | 
						|
    unload() {
 | 
						|
        if (this.image) {
 | 
						|
            if (this.image.parent) {
 | 
						|
                this.image.parent.removeChild(this.image)
 | 
						|
            }
 | 
						|
 | 
						|
            if (this.scatter) {
 | 
						|
                this.scatter.killAnimation()
 | 
						|
                this.image.scatter = null
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Removes the map, freeing all memory ba flushing handlers and removing the image.
 | 
						|
     *
 | 
						|
     * @public
 | 
						|
     * @memberof GeoMap
 | 
						|
     */
 | 
						|
    remove() {
 | 
						|
        if (this.image) this.image.mask = null
 | 
						|
 | 
						|
        this.removeFrame()
 | 
						|
        this.onTransform.empty()
 | 
						|
        this.onLoad.empty()
 | 
						|
 | 
						|
        this.unload()
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Is called when the scatter object is transformed.
 | 
						|
     *
 | 
						|
     * @private
 | 
						|
     * @param {ScatterEvent}[e] - Contains informations on how the element was transformed.
 | 
						|
     */
 | 
						|
    transformed(e) {
 | 
						|
        this.onTransform.call(this, e)
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Wrapps the display object around a scatter object.
 | 
						|
     * creates the image.
 | 
						|
     *
 | 
						|
     * @private
 | 
						|
     * @param {DisplayObject} displayObject - Defines the display object that will be wrapped inside the scatter object.
 | 
						|
     * @param {PIXI.Container} container - Defines the frame in which the map will be displayed.
 | 
						|
     */
 | 
						|
    load(image, renderer, frame = null, scatter = null) {
 | 
						|
        if (this.debug) console.log('Load image: ', image, frame)
 | 
						|
 | 
						|
        this._image = image
 | 
						|
        if (frame) this.setFrame(frame)
 | 
						|
 | 
						|
        let scatterOpts = Object.assign({
 | 
						|
            cover: this.cover,
 | 
						|
            scaleable: this.scaleable,
 | 
						|
            translatable: this.translatable,
 | 
						|
            rotatable: this.rotatable,
 | 
						|
            debug: this.debug,
 | 
						|
            startScale: this.startScale,
 | 
						|
            minScale: this.minScale,
 | 
						|
            maxScale: this.maxScale,
 | 
						|
            onTransform: this.transformed.bind(this)
 | 
						|
        })
 | 
						|
 | 
						|
        this.scatter = new MapObjectScatter(image, renderer, scatterOpts)
 | 
						|
        this.image.scatter = scatter == null ? this.scatter : scatter
 | 
						|
 | 
						|
        this.onLoad.call(this)
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Creates an identical copy of the current map.
 | 
						|
     */
 | 
						|
    clone() {
 | 
						|
        console.error('Method clone() is abstract. Overload it in subclass', this)
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     *
 | 
						|
     * Moves the 'camera' to another location on the map.
 | 
						|
     *
 | 
						|
     * @abstract
 | 
						|
     * @param {object} coordinates - Target coordinates in form {x: latitude, y: longitude}
 | 
						|
     * @param {number} scale - Target scale.
 | 
						|
     */
 | 
						|
    moveTo(coordinates, zoom = null, { animate = false } = {}) {
 | 
						|
        console.error(this, 'The method moveTo(coordinates, zoom, opts) has not been defined in child class.')
 | 
						|
    }
 | 
						|
 | 
						|
    zoomTo(zoomFactor) {
 | 
						|
        console.error('Method zoomTo() is abstract. Overload it in subclass', this, zoomFactor)
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Reloads the textures of the image, when it was unloaded and is required again.
 | 
						|
     *
 | 
						|
     * @memberof GeoMap
 | 
						|
     */
 | 
						|
    reloadImage() {
 | 
						|
        console.error(`Call of abstract method reloadImage(). Overwrite in subclass.`, this)
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Unloads the textures of the image, when not in use.
 | 
						|
     *
 | 
						|
     * @memberof GeoMap
 | 
						|
     */
 | 
						|
    unloadImage() {
 | 
						|
        console.error(`Call of abstract method unloadImage(). Overwrite in subclass.`, this)
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Transforms a point at pixel position {x,y}
 | 
						|
     * to a coordinate with latitude and longitude.
 | 
						|
     *
 | 
						|
     *
 | 
						|
     * @param {object} point - Point in form of {x, y}.
 | 
						|
     * @returns {object} - Coordinates on the map in form of {x: latitude, y: longitude}.
 | 
						|
     */
 | 
						|
    coordinatesFromPoint(point) {
 | 
						|
        let coords = this.mapProjection.toCoordinates(this.toRelativePosition(point))
 | 
						|
        return coords
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Transform coordinates in the map into pixel positions on the deep zoom image.
 | 
						|
     *
 | 
						|
     * @param {object} coords - Coordinates of a map position in form {x: latitude, y: longitude}.
 | 
						|
     * @return {Point} - Returns a image position in form of {x: x, y: y}.
 | 
						|
     */
 | 
						|
    coordinatesToPoint(coordinates) {
 | 
						|
        return this.toAbsolutePixelCoordinates(this.mapProjection.toPixel(coordinates))
 | 
						|
    }
 | 
						|
 | 
						|
    toRelativePosition(point) {
 | 
						|
        let _point = new PIXI.Point()
 | 
						|
 | 
						|
        _point.x = point.x / this.width
 | 
						|
        _point.y = point.y / this.height
 | 
						|
 | 
						|
        return _point
 | 
						|
    }
 | 
						|
 | 
						|
    toAbsolutePixelCoordinates(point) {
 | 
						|
        let _point = new PIXI.Point()
 | 
						|
        _point.x = point.x * this.width
 | 
						|
        _point.y = point.y * this.height
 | 
						|
 | 
						|
        return _point
 | 
						|
    }
 | 
						|
 | 
						|
    get width() {
 | 
						|
        return this.image.scatter.width / this.image.scatter.scale
 | 
						|
    }
 | 
						|
 | 
						|
    get height() {
 | 
						|
        return this.image.scatter.height / this.image.scatter.scale
 | 
						|
    }
 | 
						|
 | 
						|
    get distance() {
 | 
						|
        console.error('Overload get distance in subclass.')
 | 
						|
    }
 | 
						|
 | 
						|
    get alpha() {
 | 
						|
        return this._alpha
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns a screen point for a coordinate.
 | 
						|
     */
 | 
						|
    positionInWindow(coordinates) {
 | 
						|
        let pos = this.coordinatesToPoint(coordinates)
 | 
						|
 | 
						|
        let framePos = {
 | 
						|
            x: this.image.position.x + pos.x * this.image.scale.x,
 | 
						|
            y: this.image.position.y + pos.y * this.image.scale.y
 | 
						|
        }
 | 
						|
 | 
						|
        return framePos
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns the coordinates for a screen point.
 | 
						|
     */
 | 
						|
    coordinatesInWindow(point) {
 | 
						|
        let mapPos = {
 | 
						|
            x: (point.x - this.image.position.x) / this.image.scale.x,
 | 
						|
            y: (point.y - this.image.position.y) / this.image.scale.y
 | 
						|
        }
 | 
						|
 | 
						|
        let coords = this.coordinatesFromPoint(mapPos)
 | 
						|
        return coords
 | 
						|
    }
 | 
						|
 | 
						|
    removeFrame() {
 | 
						|
        this.frame = null
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * The complete Triforce, or one or more components of the Triforce.
 | 
						|
     * @typedef {Object} Frame
 | 
						|
     * @property {number} x - X position of the frame.
 | 
						|
     * @property {number} y - Y position of the frame.
 | 
						|
     * @property {number} width - Width of the frame.
 | 
						|
     * @property {number} height - Height od the frame.
 | 
						|
     * @property {Point} localCenter - Local center of the map. 
 | 
						|
     * @property {Point} center - Global center of the map.
 | 
						|
 | 
						|
     */
 | 
						|
 | 
						|
    /**
 | 
						|
     * Sets the frame if the map.
 | 
						|
     *
 | 
						|
     * Frame is the display in which the map is shown.
 | 
						|
     * Normally it's the app, but it can be another element,
 | 
						|
     * for example when in a submap.
 | 
						|
     *
 | 
						|
     * @param {Frame} frame
 | 
						|
     * @memberof GeoMap
 | 
						|
     */
 | 
						|
    setFrame(frame) {
 | 
						|
        this.frame = frame
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Gets the frame if the map.
 | 
						|
     *
 | 
						|
     * Frame is the display in which the map is shown.
 | 
						|
     * Normally it's the app, but it can be another element,
 | 
						|
     * for example when in a submap.
 | 
						|
     *
 | 
						|
     * @returns {Frame} - Returns the frame of the map.
 | 
						|
     * @memberof GeoMap
 | 
						|
     */
 | 
						|
    getFrame() {
 | 
						|
        let frame = {
 | 
						|
            x: 0,
 | 
						|
            y: 0,
 | 
						|
            width: 0,
 | 
						|
            height: 0
 | 
						|
        }
 | 
						|
 | 
						|
        if (this.frame) {
 | 
						|
            //The app does not have an x and y attribute.
 | 
						|
            //Therefore we need the additional check.
 | 
						|
            frame = {
 | 
						|
                x: this.frame.x ? this.frame.x : 0,
 | 
						|
                y: this.frame.y ? this.frame.y : 0,
 | 
						|
                width: this.frame.width,
 | 
						|
                height: this.frame.height
 | 
						|
            }
 | 
						|
        } else if (this.image.parent) {
 | 
						|
            let parent = this.image.parent
 | 
						|
 | 
						|
            for (let key of Object.keys(frame)) {
 | 
						|
                frame[key] = parent[key]
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        frame.localCenter = {
 | 
						|
            x: frame.width / 2,
 | 
						|
            y: frame.height / 2
 | 
						|
        }
 | 
						|
 | 
						|
        frame.center = {
 | 
						|
            x: frame.x + frame.localCenter.x,
 | 
						|
            y: frame.y + frame.localCenter.y
 | 
						|
        }
 | 
						|
 | 
						|
        return frame
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Retrieves all maps from a json object.
 | 
						|
     *
 | 
						|
     * @static
 | 
						|
     * @param {object} json - The parsed map data object.
 | 
						|
     * @returns {object} - Returns an object with the names as keys and the GeoMaps as value.
 | 
						|
     * @memberof GeoMap
 | 
						|
     */
 | 
						|
    static allFromJson(json, root = './') {
 | 
						|
        let error = { message: '' }
 | 
						|
        let maps = {}
 | 
						|
        if (GeoMap._validateJson(json, error)) {
 | 
						|
            for (let [mapname, data] of Object.entries(json)) {
 | 
						|
                data.tiles.path = root + data.tiles.path
 | 
						|
                maps[mapname] = GeoMap._createMap(data)
 | 
						|
                maps[mapname].name = mapname
 | 
						|
            }
 | 
						|
        } else console.error('Could not validate JSON: ' + error.message)
 | 
						|
 | 
						|
        return maps
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     *Extracts a single map from a JSON map file.
 | 
						|
     *
 | 
						|
     * @static
 | 
						|
     * @param {string} map - Name of the map.
 | 
						|
     * @param {object} json - Parsed json object.
 | 
						|
     * @returns {GeoMap} - Returns a geomap, if the map was in the data and valid. Otherwise false.
 | 
						|
     * @memberof GeoMap
 | 
						|
     */
 | 
						|
    static mapFromJson(map, json) {
 | 
						|
        if (json[map]) {
 | 
						|
            const data = json[map]
 | 
						|
            if (this._validJsonMap(data)) return GeoMap._createMap(data)
 | 
						|
            else console.error('Map was not in a valid format.')
 | 
						|
        } else console.error('Map was not in data.')
 | 
						|
 | 
						|
        return null
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Validates if the map data contains valid data
 | 
						|
     * for creating the maps.
 | 
						|
     *
 | 
						|
     * @static
 | 
						|
     * @private
 | 
						|
     * @param {object} json - The object containing multiple map data sets.
 | 
						|
     * @param {error-object} error - An object that contains an parameter message: {message = ""}. This is faking a call by reference.
 | 
						|
     * @returns {boolean} - True if all sets were valid. False otherwise.
 | 
						|
     * @memberof GeoMap
 | 
						|
     */
 | 
						|
    static _validateJson(json, error) {
 | 
						|
        let isValid = true
 | 
						|
 | 
						|
        if (Object.keys(json).length == 0) {
 | 
						|
            isValid = false
 | 
						|
            error.message += 'The provided JSON object did not contain any items.'
 | 
						|
        }
 | 
						|
        for (let [name, data] of Object.entries(json)) {
 | 
						|
            if (!GeoMap._validJsonMap(data)) {
 | 
						|
                error.message += `${name} was not valid. `
 | 
						|
                isValid = false
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if (error.message != '') error.message = 'Maps could not be validated: ' + error.message
 | 
						|
 | 
						|
        return isValid
 | 
						|
    }
 | 
						|
 | 
						|
    static changePathsInJson(json, path) {
 | 
						|
        for (let key of Object.keys(json)) {
 | 
						|
            json[key].icon = path + json[key].icon
 | 
						|
            json[key].tiles.path = path + json[key].tiles.path
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     *Validates of a single data set contains the valid data for creating a map.
 | 
						|
     *
 | 
						|
     * @static
 | 
						|
     * @private
 | 
						|
     * @param {object} json - The object containing a single set of map data.
 | 
						|
     * @returns {boolean} - True if valid, otherwise false.
 | 
						|
     * @memberof GeoMap
 | 
						|
     */
 | 
						|
    static _validJsonMap(json) {
 | 
						|
        let isJsonValid = true
 | 
						|
        const required = ['projection', 'type', 'icon']
 | 
						|
        for (const requirement of Object.values(required)) {
 | 
						|
            if (!json.hasOwnProperty(requirement)) {
 | 
						|
                isJsonValid = false
 | 
						|
                break
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if (isJsonValid && json.type.toLowerCase() == 'deepzoom') {
 | 
						|
            if (!json.hasOwnProperty('tiles')) isJsonValid = false
 | 
						|
        }
 | 
						|
 | 
						|
        return isJsonValid
 | 
						|
    }
 | 
						|
 | 
						|
    static _createMap(data) {
 | 
						|
        switch (data.type.toLowerCase()) {
 | 
						|
            case 'deepzoom':
 | 
						|
                return GeoMap._createDeepZoomMap(data)
 | 
						|
            default:
 | 
						|
                console.error(`Datatype is invalid or not implemented yet: ${data.type}`)
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    static _createDeepZoomMap(data) {
 | 
						|
        const projection = GeoMap._getProjectionByName(data.projection)
 | 
						|
        const tilesConfig = data.tiles
 | 
						|
        const options = data.options
 | 
						|
 | 
						|
        const mapProjection = new DeepZoomMapProjection(projection, tilesConfig)
 | 
						|
        return new DeepZoomMap(mapProjection, tilesConfig, options)
 | 
						|
    }
 | 
						|
 | 
						|
    static _getProjectionByName(projection) {
 | 
						|
        switch (projection.toLowerCase()) {
 | 
						|
            case 'mercator':
 | 
						|
                return new Mercator()
 | 
						|
            default:
 | 
						|
                console.error(`Projection is invalid or not implemented yet ${projection}.`)
 | 
						|
                return null
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
GeoMap.counter = 0
 | 
						|
 | 
						|
/**
 | 
						|
 *  The DeepZoomMap class extends the GeoMap to create
 | 
						|
 *   maps as deepzoom images from maptiles.
 | 
						|
 *
 | 
						|
 * @export
 | 
						|
 * @class DeepZoomMap
 | 
						|
 * @extends {GeoMap}
 | 
						|
 */
 | 
						|
export class DeepZoomMap extends GeoMap {
 | 
						|
    /**
 | 
						|
     * @constructor
 | 
						|
     * @param {object} tilesConfig - The tiles config object, that defines at what path and in which format the tiles are.
 | 
						|
     * @param {MapProjection} mapProjection - A MapProjection object, that contains informations of how the given map has to be interpreted.
 | 
						|
     * @param {object} opts - Additional options to specify the behaviour of the deep zoom image.
 | 
						|
     */
 | 
						|
    constructor(mapProjection, tilesConfig, opts = {}) {
 | 
						|
        opts = Object.assign(
 | 
						|
            {
 | 
						|
                maxScale: Math.min(tilesConfig.width, tilesConfig.height) / tilesConfig.tileSize,
 | 
						|
                minScale: mapProjection.getMinScale,
 | 
						|
                highResolution: true,
 | 
						|
                debug: false
 | 
						|
            },
 | 
						|
            opts
 | 
						|
        )
 | 
						|
        super(mapProjection, opts)
 | 
						|
 | 
						|
        this.tilesConfig = tilesConfig
 | 
						|
        this._verifyMapProjection()
 | 
						|
    }
 | 
						|
 | 
						|
    _verifyMapProjection() {
 | 
						|
        if (!(this.mapProjection instanceof MapProjection)) {
 | 
						|
            console.error('Use the MapProjection object for creating maps!')
 | 
						|
        } else {
 | 
						|
            if (!(this.mapProjection instanceof DeepZoomMapProjection)) {
 | 
						|
                console.error('Use the DeepZoomMapProjection object.')
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Creates the DeepZoomImage.
 | 
						|
     *
 | 
						|
     * @private
 | 
						|
     */
 | 
						|
    load(container = null, scatter = null) {
 | 
						|
        if (!this.mapProjection.app) console.error('App was not set in the mapProjection.')
 | 
						|
        this.info = new DeepZoomInfo(this.tilesConfig)
 | 
						|
        let image = new DeepZoomImage(this.info, {
 | 
						|
            app: this.mapProjection.app,
 | 
						|
            alpha: this.alpha,
 | 
						|
            debug: this.debug,
 | 
						|
            world: scatter == null ? scatter : scatter.getWorldScatter()
 | 
						|
        })
 | 
						|
 | 
						|
        super.load(image, container, scatter)
 | 
						|
 | 
						|
        if (this.debug) console.log('Loaded image: ', image, 'With options: ', this.info)
 | 
						|
    }
 | 
						|
 | 
						|
    unloadImage() {
 | 
						|
        if (this.image) {
 | 
						|
            this.image.deactivate()
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    reloadImage() {
 | 
						|
        this.image.activate()
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Event function that is invoked by the deep zoom image, when it has been transformed.
 | 
						|
     *
 | 
						|
     * @private
 | 
						|
     * @param {ScatterEvent} e
 | 
						|
     */
 | 
						|
    transformed(e) {
 | 
						|
        this.image.transformed(e)
 | 
						|
        super.transformed(e)
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Jumps to a certain point on the map and adjusts the size of the
 | 
						|
     * map depending on the distance (zoom).
 | 
						|
     *
 | 
						|
     *
 | 
						|
     * @param {*} coordinates
 | 
						|
     * @param {*} [zoom=null]
 | 
						|
     * @param {*} [{ animate = 0 }={}]
 | 
						|
     * @memberof DeepZoomMap
 | 
						|
     */
 | 
						|
    moveTo(coordinates, zoom = null, { animate = 0 } = {}) {
 | 
						|
        if (this.image.scatter == null) {
 | 
						|
            return
 | 
						|
        }
 | 
						|
        //TODO: This is jaggy on submaps. Make it more smooth. Severin Opel 08-05-2018
 | 
						|
        if (zoom) {
 | 
						|
            /**
 | 
						|
             * Zoom's the view to a specified distance in DZI-Levels.
 | 
						|
             * The input level is clipped to allowed levels.
 | 
						|
             * */
 | 
						|
            let targetScale = this.scaleForFloatingLevel(zoom)
 | 
						|
 | 
						|
            // Don't scale beyond boundaries.
 | 
						|
            if (this.maxScale) targetScale = Math.min(this.maxScale, targetScale)
 | 
						|
            if (this.minScale) targetScale = Math.max(targetScale, this.minScale)
 | 
						|
 | 
						|
            this.image.scatter.requestScale(targetScale)
 | 
						|
        }
 | 
						|
 | 
						|
        if (coordinates) {
 | 
						|
            //Get pixel position of lat lang values
 | 
						|
            let point = this.coordinatesToPoint(coordinates)
 | 
						|
 | 
						|
            let containerCenter
 | 
						|
            if (this.frame) {
 | 
						|
                containerCenter = this.getFrame().localCenter
 | 
						|
            } else {
 | 
						|
                containerCenter = {
 | 
						|
                    x: this.image.parent.width / 2,
 | 
						|
                    y: this.image.parent.height / 2
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            //Calculate the delta depending on image position and center the
 | 
						|
            // desired position in the frame.
 | 
						|
            let delta = {
 | 
						|
                x: containerCenter.x - point.x * this.image.scatter.scale - this.scatter.position.x,
 | 
						|
                y: containerCenter.y - point.y * this.image.scatter.scale - this.scatter.position.y
 | 
						|
            }
 | 
						|
 | 
						|
            if (animate > 0) {
 | 
						|
                let last = 0
 | 
						|
                TweenMax.to({ x: 0, y: 0 }, animate, {
 | 
						|
                    ease: Power0.easeNone,
 | 
						|
                    x: delta.x,
 | 
						|
                    y: delta.y,
 | 
						|
                    onUpdateParams: ['{self}'],
 | 
						|
                    onUpdate: tween => {
 | 
						|
                        let step = tween.ratio - last
 | 
						|
                        last = tween.ratio
 | 
						|
                        this.image.scatter.transform(Points.multiplyScalar(delta, step), /*ratio*/ 1, 0, { x: 0, y: 0 })
 | 
						|
                    }
 | 
						|
                })
 | 
						|
            } else {
 | 
						|
                this.image.scatter.transform(delta, /*ratio*/ 1, 0, { x: 0, y: 0 })
 | 
						|
            }
 | 
						|
        }
 | 
						|
        // return this.image.scatter.position
 | 
						|
    }
 | 
						|
 | 
						|
    scaleForFloatingLevel(level) {
 | 
						|
        level = Math.max(0, level - this.image.info.baseLevel)
 | 
						|
        return Math.pow(2, level) / this.image.resolution
 | 
						|
    }
 | 
						|
 | 
						|
    floatingLevelForScale(scale) {
 | 
						|
        let level = Math.log2(scale * this.image.resolution)
 | 
						|
        let newLevel = this.image.info.baseLevel + Math.max(level, 0)
 | 
						|
        return Math.min(newLevel, this.image.info.maxLoadableLevel)
 | 
						|
    }
 | 
						|
 | 
						|
    get distance() {
 | 
						|
        return this.floatingLevelForScale(this.scatter.scale)
 | 
						|
    }
 | 
						|
 | 
						|
    clone(container, scatter = null) {
 | 
						|
        const map = new DeepZoomMap(this.mapProjection, this.tilesConfig, {
 | 
						|
            alpha: this.alpha,
 | 
						|
            cover: this.cover,
 | 
						|
            debug: this.debug,
 | 
						|
            startScale: this.startScale,
 | 
						|
            minScale: this.minScale,
 | 
						|
            maxScale: this.maxScale,
 | 
						|
            rotatable: this.rotatable,
 | 
						|
            translatable: this.translatable,
 | 
						|
            scalable: this.scalable,
 | 
						|
            viewport: this.viewport
 | 
						|
        })
 | 
						|
 | 
						|
        return map
 | 
						|
    }
 | 
						|
 | 
						|
    tint() {
 | 
						|
        let color = DeepZoomMap.tintcolors[DeepZoomMap.tintcolor++ % DeepZoomMap.tintcolors.length]
 | 
						|
 | 
						|
        this._forEachTile(tile => {
 | 
						|
            tile.tint = color
 | 
						|
        })
 | 
						|
    }
 | 
						|
 | 
						|
    _forEachTile(callback) {
 | 
						|
        this.image.children[0].children.forEach(tiles => {
 | 
						|
            tiles.children.forEach(callback)
 | 
						|
        })
 | 
						|
    }
 | 
						|
 | 
						|
    setAlpha(alpha) {
 | 
						|
        this._forEachTile(tile => {
 | 
						|
            tile.alpha = alpha
 | 
						|
        })
 | 
						|
    }
 | 
						|
 | 
						|
    //    /** Returns the tile layer level that corresponds to the given scale.
 | 
						|
    //  * @param {number} scale - the scale factor
 | 
						|
    //  **/
 | 
						|
    // levelForScale(scale) {
 | 
						|
    //     let level = Math.round(Math.log2(scale * this.resolution)) // Math.floor(Math.log2(event.scale))+1
 | 
						|
    //     let newLevel = this.info.baseLevel + Math.max(level, 0)
 | 
						|
    //     return Math.min(newLevel, this.info.maxLoadableLevel)
 | 
						|
    // }
 | 
						|
 | 
						|
    // /**
 | 
						|
    //  * Returns the scale factor that correspond to a given level.
 | 
						|
    //  * @param {number} level - the requested level
 | 
						|
    //  * @returns {number} - Returns the scale level for the given level.
 | 
						|
    //  */
 | 
						|
    // scaleForLevel(level){
 | 
						|
    //     level = Math.max(0, level - this.info.baseLevel)
 | 
						|
    //     return Math.pow(2, level) / this.resolution
 | 
						|
    // }
 | 
						|
}
 | 
						|
 | 
						|
DeepZoomMap.tintcolors = [0xff0000, 0xff00ff, 0xffff00, 0x00ff00, 0x00ffff, 0x0000ff]
 | 
						|
DeepZoomMap.tintcolor = 0
 | 
						|
 | 
						|
/**
 | 
						|
 * ImageMap extends GeoMap to display simple images
 | 
						|
 * as maps.
 | 
						|
 *
 | 
						|
 * @export
 | 
						|
 * @class ImageMap
 | 
						|
 * @extends {GeoMap}
 | 
						|
 */
 | 
						|
export class ImageMap extends GeoMap {
 | 
						|
    constructor(sprite, mapProjection, opts = {}) {
 | 
						|
        super(mapProjection, opts)
 | 
						|
        if (this.debug) console.log('Construct Image Map', sprite, mapProjection, opts)
 | 
						|
 | 
						|
        this.sprite = sprite
 | 
						|
 | 
						|
        this.baseZoomHeight = opts.baseZoomHeight ? opts.baseZoomHeight : this.sprite.texture.height
 | 
						|
    }
 | 
						|
 | 
						|
    load(container = null, scatter = null) {
 | 
						|
        super.load(this.sprite, container, scatter)
 | 
						|
        this.image.alpha = this.alpha
 | 
						|
        this.image.interactive = true
 | 
						|
    }
 | 
						|
 | 
						|
    clone(container = null, scatter = null) {
 | 
						|
        const map = new ImageMap(new PIXI.Sprite(this.sprite.texture), this.mapProjection, {
 | 
						|
            alpha: this.alpha,
 | 
						|
            cover: this.cover,
 | 
						|
            debug: this.debug,
 | 
						|
            startScale: this.startScale,
 | 
						|
            minScale: this.minScale,
 | 
						|
            maxScale: this.maxScale,
 | 
						|
            rotatable: this.rotatable,
 | 
						|
            translatable: this.translatable,
 | 
						|
            scalable: this.scalable,
 | 
						|
            viewport: this.viewport
 | 
						|
        })
 | 
						|
        map.load(container, scatter)
 | 
						|
 | 
						|
        return map
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     *
 | 
						|
     *
 | 
						|
     * @param {*} coordinates
 | 
						|
     * @param {*} [zoom=null]
 | 
						|
     * @param {*} [{ animate = 0 }={}]
 | 
						|
     * @memberof ImageMap
 | 
						|
     */
 | 
						|
    moveTo(coordinates, zoom = null, { animate = 0 } = {}) {
 | 
						|
        if (this.image.scatter == null) {
 | 
						|
            return
 | 
						|
        }
 | 
						|
        //TODO: This is jaggy on submaps. Make it more smooth. Severin Opel 08-05-2018
 | 
						|
        if (zoom) {
 | 
						|
            /**
 | 
						|
             * Zoom's the view to a specified distance in DZI-Levels.
 | 
						|
             * The input level is clipped to allowed levels.
 | 
						|
             * */
 | 
						|
 | 
						|
            let targetScale = zoom * this.zoomFactor
 | 
						|
 | 
						|
            // Don't scale beyond boundaries.
 | 
						|
            if (this.maxScale) targetScale = Math.min(this.maxScale, targetScale)
 | 
						|
 | 
						|
            if (this.minScale) targetScale = Math.max(targetScale, this.minScale)
 | 
						|
 | 
						|
            this.image.scatter.requestScale(targetScale)
 | 
						|
        }
 | 
						|
 | 
						|
        //coordinates = null
 | 
						|
        if (coordinates) {
 | 
						|
            //Get pixel position of lat lang values
 | 
						|
            let point = this.coordinatesToPoint(coordinates)
 | 
						|
 | 
						|
            let containerCenter
 | 
						|
            if (this.frame) {
 | 
						|
                containerCenter = this.getFrame().localCenter
 | 
						|
            } else {
 | 
						|
                containerCenter = {
 | 
						|
                    x: this.image.parent.width / 2,
 | 
						|
                    y: this.image.parent.height / 2
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            //Calculate the delta depending on image position and center the
 | 
						|
            // desired position in the frame.
 | 
						|
 | 
						|
            let delta = {
 | 
						|
                x: containerCenter.x - point.x * this.image.scatter.scale - this.scatter.position.x,
 | 
						|
                y: containerCenter.y - point.y * this.image.scatter.scale - this.scatter.position.y
 | 
						|
            }
 | 
						|
 | 
						|
            if (animate > 0) {
 | 
						|
                let last = 0
 | 
						|
                TweenMax.to({ x: 0, y: 0 }, animate, {
 | 
						|
                    ease: Power0.easeNone,
 | 
						|
                    x: delta.x,
 | 
						|
                    y: delta.y,
 | 
						|
                    onUpdateParams: ['{self}'],
 | 
						|
                    onUpdate: tween => {
 | 
						|
                        let step = tween.ratio - last
 | 
						|
                        last = tween.ratio
 | 
						|
                        this.image.scatter.transform(Points.multiplyScalar(delta, step), /*ratio*/ 1, 0, { x: 0, y: 0 })
 | 
						|
                    }
 | 
						|
                })
 | 
						|
            } else {
 | 
						|
                this.image.scatter.transform(delta, /*ratio*/ 1, 0, { x: 0, y: 0 })
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    get zoom() {
 | 
						|
        return this.scatter.scale / this.zoomFactor
 | 
						|
    }
 | 
						|
 | 
						|
    get zoomFactor() {
 | 
						|
        let factor = this.baseZoomHeight / this.sprite.texture.height
 | 
						|
        return factor
 | 
						|
    }
 | 
						|
}
 |