From ff0606d0a75cf0513bb8f6e59a8c5dea02d66500 Mon Sep 17 00:00:00 2001 From: Severin Opel Date: Mon, 4 Nov 2019 18:20:32 +0100 Subject: [PATCH] Removing layer dependency from geolayers and refactoring maps. --- dist/iwmlib.js | 25 + dist/iwmlib.pixi.js | 2309 +++++++++++++++++--------------- lib/pixi/bundle.js | 6 +- lib/pixi/maps/geographics.html | 24 +- lib/pixi/maps/geographics.js | 40 +- lib/pixi/maps/geolayer.js | 241 ++-- lib/pixi/maps/map.js | 35 +- lib/pixi/maps/mapapp.html | 2 - lib/pixi/maps/mapapp.js | 93 +- lib/pixi/maps/mapview.js | 79 +- lib/pixi/maps/overlay.js | 2 +- lib/pixi/maps/scatter.js | 3 +- 12 files changed, 1519 insertions(+), 1340 deletions(-) diff --git a/dist/iwmlib.js b/dist/iwmlib.js index 9c2515a..e107bce 100644 --- a/dist/iwmlib.js +++ b/dist/iwmlib.js @@ -2540,6 +2540,25 @@ result[id] = this.getPosition(event); break } + // case 'TouchEvent': + // // Needs to be observed: Perhaps changedTouches are all we need. If so + // // we can remove the touchEventKey default parameter + // if (touchEventKey == 'all') { + // for(let t of event.targetTouches) { + // result[t.identifier.toString()] = this.getPosition(t) + // } + // for(let t of event.changedTouches) { + // result[t.identifier.toString()] = this.getPosition(t) + // } + // } + // else { + // for(let t of event.changedTouches) { + // result[t.identifier.toString()] = this.getPosition(t) + // } + // } + // break + default: + break } return result } @@ -5635,6 +5654,8 @@ let bottom = parseFloat(this.element.style.bottom); this.element.style.bottom = bottom - delta.y + 'px'; break + default: + break } //console.log("onResize", this.onResize) if (this.onResize) { @@ -6057,6 +6078,8 @@ x = bbRight; if (!this.useEventPosWithBoundingBox) y = (bbTop + bbBottom) / 2; break + default: + break } } @@ -6112,6 +6135,8 @@ x += this.notchSize * 2; x += this.posOffset; break + default: + break } this.placeOrigin(x, y); } diff --git a/dist/iwmlib.pixi.js b/dist/iwmlib.pixi.js index 49ce00d..35b0415 100644 --- a/dist/iwmlib.pixi.js +++ b/dist/iwmlib.pixi.js @@ -6185,6 +6185,25 @@ result[id] = this.getPosition(event); break } + // case 'TouchEvent': + // // Needs to be observed: Perhaps changedTouches are all we need. If so + // // we can remove the touchEventKey default parameter + // if (touchEventKey == 'all') { + // for(let t of event.targetTouches) { + // result[t.identifier.toString()] = this.getPosition(t) + // } + // for(let t of event.changedTouches) { + // result[t.identifier.toString()] = this.getPosition(t) + // } + // } + // else { + // for(let t of event.changedTouches) { + // result[t.identifier.toString()] = this.getPosition(t) + // } + // } + // break + default: + break } return result } @@ -17373,13 +17392,14 @@ }, opts ); + super(displayObject, renderer, opts); + if (!renderer) { console.error('Renderer was not set!'); return } - super(displayObject, renderer, opts); this.cover = opts.cover; } @@ -17771,8 +17791,10 @@ } unload() { - this.image.parent.removeChild(this.image); - this.image.scatter = null; + if (this.image) { + if (this.image.parent) this.image.parent.removeChild(this.image); + this.image.scatter = null; + } } /** @@ -17822,6 +17844,17 @@ this.image.scatter = scatter == null ? this.scatter : scatter; this.onLoad.call(this); + + // Object.assign(this.image, { + // set scatter (value) { + // console.trace("Scatter set.") + // this._scatter = value + // }, + // get scatter (){ + // console.trace("Get Scatter.") + // return this._scatter + // } + // }) } /** @@ -17905,21 +17938,6 @@ return _point } - // /** - // * Appends the object to a PIXI container. This is important, - // * to notify the map, that it's parent has changed. - // * - // * If you want to use PIXI's addChild, make sure you call - // * appended right afterwards. - // * - // * @param {PIXI.Container} container - // * @returns Returns the map object to allow chaining. - // */ - // appendTo(container) { - // container.addChild(this.image) - // return this.appended(container) - // } - get width() { return this.image.scatter.width / this.image.scatter.scale } @@ -18139,6 +18157,8 @@ return null } } + + } GeoMap.counter = 0; @@ -18404,6 +18424,7 @@ load(container = null, scatter = null) { super.load(this.sprite, container, scatter); + console.log('LOADED'); this.image.alpha = this.alpha; this.image.interactive = true; } @@ -18567,16 +18588,10 @@ * @param {number} [zoom = 0] - Defines the startup zoom of the app. Note that this is just a request. * The MapView will prioritize a full scale app, than displaying the demanded zoom factor */ - constructor({ - map = null, - focus = null, - zoom = null, - viewport = { min: { x: -85, y: -180 }, max: { x: 85, y: 180 } } - } = {}) { + constructor({ focus = null, zoom = null, viewport = { min: { x: -85, y: -180 }, max: { x: 85, y: 180 } } } = {}) { this.viewport = viewport; this._focus = focus; this._zoom = zoom; - this._map = map; this.referenceHeight = 256; } @@ -18587,66 +18602,53 @@ return this._zoom } - get map() { - return this._map + update(map) { + map.moveTo(this._focus, this._zoom); } - /** - * Sets the map to the given focuspoint and zoom factor. - */ - setMap(map) { - this._map = map; - this.update(); - return true + transformed(map) { + this.updateZoom(map); + this.updateFocusPoint(map); } - update() { - this.map.moveTo(this._focus, this._zoom); + applyCameraPosition(map) { + this.updateFocusPoint(map); + this.updateZoom(map); } - transformed(e) { - this.updateZoom(); - this.updateFocusPoint(); + updateFocusPoint(map) { + const frame = map.getFrame(); + this._focus = this.coordinatesFromWindowPoint(map, frame.localCenter); } - applyCameraPosition() { - this.updateFocusPoint(); - this.updateZoom(); - } - - updateFocusPoint() { - const frame = this.map.getFrame(); - this._focus = this.coordinatesFromWindowPoint(frame.localCenter); - } - - updateZoom() { + updateZoom(map) { /** * TODO: This relies on the fact, that all maps have the same tileSize, * if a set would have a smaller tileSize. Improve that. */ - if (this.map instanceof DeepZoomMap) this._zoom = this.map.floatingLevelForScale(this.map.image.scatter.scale); + if (map instanceof DeepZoomMap) this._zoom = map.floatingLevelForScale(map.image.scatter.scale); else { - this._zoom = this.map.zoom; - console.warn('Zoom is not yet correctly implemented in this Map type: ' + this.map); + this._zoom = map.zoom; + console.warn('Zoom is not yet correctly implemented in this Map type: ' + map); } } - mapPointToWindowPoint(point) { - let container = this.map.image.parent; + mapPointToWindowPoint(map, point) { + let container = map.image.parent; let _point = new PIXI.Point( - this.map.scatter.position.x + this.map.scatter.scale * point.x, - this.map.scatter.position.y + this.map.scatter.scale * point.y + map.scatter.position.x + map.scatter.scale * point.x, + map.scatter.position.y + map.scatter.scale * point.y ); return container.toGlobal(_point) } - windowPointToMapPoint(point) { - let offset = this.map.image.parent.toGlobal({ x: 0, y: 0 }); + windowPointToMapPoint(map, point) { + let offset = map.image.parent.toGlobal({ x: 0, y: 0 }); let _point = new PIXI.Point( - (point.x - this.map.scatter.position.x - offset.x) / this.map.scatter.scale, - (point.y - this.map.scatter.position.y - offset.y) / this.map.scatter.scale + (point.x - map.scatter.position.x - offset.x) / map.scatter.scale, + (point.y - map.scatter.position.y - offset.y) / map.scatter.scale ); return _point @@ -18659,18 +18661,18 @@ * @returns {{x,y}} Coordinates on the map of the provided position. * @memberof MapView */ - coordinatesFromWindowPoint(point) { + coordinatesFromWindowPoint(map, point) { let position = { - x: point.x - this.map.scatter.position.x, - y: point.y - this.map.scatter.position.y + x: point.x - map.scatter.position.x, + y: point.y - map.scatter.position.y }; let normalized = { - x: position.x / (this.map.width * this.map.scatter.scale), - y: position.y / (this.map.height * this.map.scatter.scale) + x: position.x / (map.width * map.scatter.scale), + y: position.y / (map.height * map.scatter.scale) }; - let coordinates = this.map.mapdata.toCoordinates(normalized); + let coordinates = map.mapdata.toCoordinates(normalized); return coordinates } @@ -18937,6 +18939,1080 @@ } } + /* globals PIXI */ + + class FlagType { + static get bottomLeft() { + return { x: 1, y: -1 } + } + static get bottomRight() { + return { x: -1, y: -1 } + } + static get topLeft() { + return { x: -1, y: 1 } + } + static get topRight() { + return { x: 1, y: 1 } + } + + static toString(flagType) { + let str = ''; + if (flagType.x && flagType.y) { + if (flagType.y == 1) str += 'bottom'; + else if (flagType.y == -1) str += 'top'; + else str += '_INVALID_Y_'; + + if (flagType.x == 1) str += 'Right'; + else if (flagType.x == -1) str += 'Left'; + else str += '_INVALID_X_'; + } else str = 'Invalid FlagType: ' + flagType.toString(); + + return str + } + } + + class FlagPolygon extends PIXI.Polygon { + constructor({ + type = FlagType.bottomLeft, + width = 100, + height = 30, + notchSize = 10, + notchWidth = null, + notchHeight = null, + originOffset = { x: 0, y: 0 } + } = {}) { + let points = []; + + let dimensions = { x: width, y: height }; + dimensions = Points.multiply(dimensions, type); + + notchWidth = notchWidth == null ? notchSize : notchWidth; + notchHeight = notchHeight == null ? notchSize : notchHeight; + + notchSize = { x: notchWidth, y: notchHeight }; + notchSize = Points.multiply(notchSize, type); + + originOffset = Points.multiply(originOffset, type); + + let point = new PIXI.Point(originOffset.x, originOffset.y); + points.push(point.clone()); + + point.y += notchSize.y; + points.push(point.clone()); + + point.y += dimensions.y; + points.push(point.clone()); + + point.x += dimensions.x; + points.push(point.clone()); + + point.y -= dimensions.y; + points.push(point.clone()); + + point.x -= dimensions.x - notchSize.x; + points.push(point.clone()); + + // close polygon + points.push(points[0].clone()); + + super(points); + + this.type = type; + this.dimensions = dimensions; + this.notchSize = notchSize; + this.originOffset = originOffset; + } + + getPoint(i) { + if (i >= 0) { + let idx = i * 2; + return [this.points[idx], this.points[idx + 1]] + } else { + let idx = (Math.floor(this.points.length / 2) + i) * 2; + return [this.points[idx], this.points[idx + 1]] + } + } + + get notch() { + let points = [this.getPoint(0), this.getPoint(1), this.getPoint(-2), this.getPoint(0)]; + let notchPolygon = []; + + points.forEach(point => { + notchPolygon = notchPolygon.concat(point); + }); + + return notchPolygon + } + get rect() { + let points = [this.getPoint(1), this.getPoint(2), this.getPoint(3), this.getPoint(4), this.getPoint(1)]; + + let rectPolygon = []; + points.forEach(point => { + rectPolygon = rectPolygon.concat(point); + }); + return rectPolygon + } + + placeText(text, padding) { + text.position = Points.add(this.originOffset, { x: 0, y: this.notchSize.y }); + padding = Points.multiply(padding, this.type); + text.position = Points.add(text.position, padding); + + if (this.type.y == -1) text.position.y -= text.height; + + if (this.type.x == -1) text.position.x -= text.width; + } + } + + class Flag extends PIXI.Graphics { + constructor( + { + type = FlagType.bottomLeft, + width = 100, + height = 30, + notchSize = 10, + notchWidth = null, + notchHeight = null, + originOffset = { x: 0, y: 0 } + } = {}, + nativeLines = false + ) { + super(nativeLines); + + this.flagPolygon = new FlagPolygon({ + type, + width, + height, + notchSize, + notchWidth, + notchHeight, + originOffset + }); + + this.draw(); + } + + draw() { + this.drawPolygon(this.flagPolygon); + } + + get typeName() { + return FlagType.toString(this.type) + } + } + + class Label extends PIXI.Graphics { + constructor(text, textStyle = new PIXI.TextStyle(), nativeLines = false) { + super(nativeLines); + this._text = new PIXI.Text(text, textStyle); + this.addChild(this._text); + } + + get text() { + return this._text + } + } + + /** + * GeoGraphics are graphical objects, that does not store the graphics information + * in screen space, but in geographical coordinates. Therefore GeoGraphics must be + * placed on GeoLayers to work properly. + * + * (Note: As GeoLayers are always children of a map layer. When the map is changed + * all GeoLayers are notified via the 'adaptTo(map)' method.) + * + * The geolayers forward this 'adaptTo' to all children that are GeoGraphics. + * Which adjust their so called 'point' data to the new map. + * + * @abstract + */ + class GeoGraphics { + constructor(coordinates, { scale = 1, onDraw = null, onDrawEnd = null, debug = false } = {}) { + this.coordinates = coordinates; + this.debug = debug; + this.graphics = new PIXI.Graphics(); + this.scale = scale; + this.drawHandler = new EventHandler('onDraw', { listeners: onDraw }); + this.drawEndHandler = new EventHandler('onDrawEnd', { listeners: onDrawEnd }); + this._points = null; + this._position = null; + } + + clone() { + console.error(`Call of abstract method clone(). Overwrite in subclass.`, this); + } + + _cloneOptions() { + return { + debug: this.debug, + scale: this.scale + } + } + + /** + * The _adaptCoordinates is called first by the adaptTo Method. + * Here all coordinates are transformed into point coordinates. + * This must be overloaded in subclass. + * + * @abstract + */ + _adaptCoordinates(map) { + console.error(`Call of abstract method _adaptCoordinates(map). Overwrite in subclass.`, this); + } + + /** + * Gets all screen points in a single array. + * + * @abstract + * @returns {array} - Array of all points in the GeoGraphic. + */ + _getPoints() { + console.error(`Call of abstract method _getPoints(func). Overwrite in subclass.`, this); + } + + /** + * Manipulates all points depending on a function. + * Mainly used to transform points to local space. + * + * @abstract + * @param {function} func + * @memberof GeoGraphics + */ + _manipulatePoints(func) { + console.error(`Call of abstract method _manipulatePoints(func). Overwrite in subclass.`, this); + } + + /** + * The _draw method is called last on adaptation. It creates the GraphicData + * of the specified subclass. To manipulate the style of the graphic, hook an onDraw listener + * to the GeoGraphics object. It is called before the _draw and lets the user modify color and + * lineStyle of the drawn object. + * + * Note: It could also be used for more radical manipulations on the graphics object. + * But this should be used with care. + * + * @abstract + */ + _draw() { + console.error(`Call of abstract method _draw(). Overwrite in subclass.`, this); + } + + /** + * Called by the containing geo layer, when the map changes. + */ + adaptTo(map) { + this._points = this._adaptCoordinates(map); + this._updatePosition(); + this.draw(); + } + + /** + * Redraws the graphics. + * + * This should be only called if you require an redraw independent of an adapt. + * + * @memberof GeoGraphics + */ + draw() { + this._prepareDraw(); + this.drawHandler.call(this, this.graphics); + this._draw(); + this.drawEndHandler.call(this, this.graphics); + } + + /** + * Retrieves the point data. + * Note: This data changes on adaptation. + */ + get points() { + return this._points + } + + /** + * Returns the screen position of the GeoGraphics. + */ + get position() { + return this._position + } + + // get map() { + // if ( + // this.graphics.layer && + // (this.graphics.layer instanceof GeoLayer || this.graphics.layer instanceof MapLayer) + // ) { + // return this.graphics.layer.map + // } else return null + // } + + // get mapLayer() { + // if ( + // this.graphics.layer && + // (this.graphics.layer instanceof GeoLayer || this.graphics.layer instanceof MapLayer) + // ) { + // return this.graphics.layer.mapLayer + // } else return null + // } + + /** + * Prepare draw is a private function, that prepares the graphics + * for the next draw call. It also fires the drawHandler. + * + * @private + * @memberof GeoGraphics + */ + _prepareDraw() { + this.graphics.clear(); + /* + Set a fillcolor and a stroke style for + debugging. Can be overloaded using the onDraw + event function. + */ + if (this.debug) this.graphics.beginFill(0xff00ff); + } + + _updatePosition() { + let points = this._getPoints(); + this._position = GeoGraphics.calculateCenterOfMass(points); + this._manipulatePoints(point => { + point = Points.subtract(point, this._position); + return point + }); + this.graphics.position = this._position; + } + + /** + * Calculates the center of Mass for a set of points. + * + * @static + * @param {Array} points - Array of points in the format {x: a, y:b} + * @returns {object} - Returns a point containing the center of mass of the polygon. + * @memberof GeoGraphics + */ + static calculateCenterOfMass(points) { + let com = new PIXI.Point(); + points.forEach(p => { + let point = new PIXI.Point(p.x, p.y); + com = Points.add(com, point); + }); + return Points.multiplyScalar(com, 1 / points.length) + } + } + + /** + * Represents a single point on the Map. + * + * This GeoGraphics does not provide any visual representation. + * Draw the desired shape in the onDraw callback. + */ + class GeoPoint extends GeoGraphics { + clone() { + return new GeoPoint(this.coordinates, this._cloneOptions()) + } + + _adaptCoordinates(map) { + let scale = 1; + + if (map instanceof DeepZoomMap) { + scale = map.image.scale.x; + } + + scale = scale / 4; + + return map.coordinatesToPoint(this.coordinates) + } + + _getPoints() { + return [this.points] + } + + _manipulatePoints(func) { + this._points = func(this._points); + } + + _draw() {} + } + + class GeoLine extends GeoGraphics { + /** + * @param {object} opts - Optional values + * @param {array} [opts.points=[]] - Initial points of the geo shape. + * @param {boolean} [closed=false] - Defines if the + */ + constructor(coordinates, { closed = false, size = 1, onDraw = null } = {}) { + super(coordinates, { + size, + onDraw + }); + + this._closed = closed; + } + + clone() { + return new GeoLine(this.coordinates, this._cloneOptions) + } + + _cloneOptions() { + let options = super._cloneOptions(); + Object.assign(options, { + closed: this.closed + }); + } + + /** + * Adds a point to the geo line. + */ + addPoint(coordinate) { + this.coordinates.push(coordinate); + } + + _manipulatePoints(func) { + this.points.forEach((point, idx, array) => { + array[idx] = func(point); + }); + } + + _getPoints() { + return this.points + } + + _adaptCoordinates(map) { + let points = []; + this.coordinates.forEach(point => { + points.push(map.coordinatesToPoint(point)); + }); + return points + } + + _prepareDraw() { + this.graphics.clear(); + if (this.debug) this.graphics.lineStyle(0.5, 0xff00ff); + this.drawHandler.call(this); + } + + _draw() { + /** + * This resets the fill. + * + * DISCUSS: SO: "I'm not sure how the line should be defined. + * On the one hand. The line is clearly intended to + * represent a line and not an area. On the other hand, + * why should the user be prevented from using a fill for the + * area within the line. But if he want's a fill, why don't take + * a Polygon in the first place? + * + * (But if it's a predefined GeoJSON object obtained through e.g. elasticsearch, + * then the user is not in full control of the object type and it may be a good + * addition to grant the user this additional design choice.) + * + * The opportunity to do so would result in additional conditions, when creating the + * GeoGraphics of an overlay." + * */ + this.graphics.beginFill(0, 0); + + if (this.points.length > 0) { + this.graphics.moveTo(this.points[0].x, this.points[0].y); + + for (let i = 1; i < this.points.length; i++) { + this.graphics.lineTo(this.points[i].x, this.points[i].y); + } + + if (this.closed) { + this.graphics.lineTo(this.points[0].x, this.points[0].y); + } + } + } + + get closed() { + return this._closed + } + + set closed(val) { + if (val != this._closed) { + this._closed = val; + this.draw(); + } + } + } + + class GeoShape extends GeoGraphics { + clone() { + return new GeoShape(this.coordinates, this._cloneOptions) + } + + _manipulatePoints(func) { + this.constructor._manipulatePoints(this.points, func); + } + + /** + * Mets the requirements for the _manipulatePointsMethod(). + * Primarily used in subclasses to get the processing steps from + * their superclass. + * + * @static + * @protected + * @param {Array.} points - The points array that shold be manipulated. + * @param {function} func - The function that changes the single point value. Has to return a new point. + * @memberof GeoShape + */ + static _manipulatePoints(points, func) { + points.forEach((pointArray, arrIdx) => { + pointArray.forEach((point, idx) => { + points[arrIdx][idx] = func(point); + }); + }); + } + + _getPoints() { + return this.constructor._getPointsFrom(this.points) + } + + /** + * Returns all points of a polygon array. + * Useful for when getting sub polygons in child class. + */ + static _getPointsFrom(shape) { + let concatArray = []; + shape.forEach(array => { + concatArray = concatArray.concat(array); + }); + + return concatArray + } + + _adaptCoordinates(map) { + let val = this.constructor._adaptPoint(this.coordinates, map); + return val + } + + static _adaptPoint(coordinates, map) { + return coordinates.map(array => { + return array.map(point => { + return map.coordinatesToPoint(point) + }) + }) + } + + _draw() { + this._drawFrom(this.points); + this.graphics.position = this.position; + } + + _drawFrom(shape) { + const { polygon, hole } = this.constructor._pointsToShape(shape); + this._drawShape(polygon, hole); + } + + /** + * Separates the points array into it's two parts: + * - the solid polygon + * - a hole that is cut into the polygon (optional) + * + * @private + * @returns {object} - Returns an object containing the polygon and the hole in the form of {polygon: [...PIXI.Point], hole: [...PIXI.Point]} + */ + static _pointsToShape(points) { + let polygon = GeoShape._transformToPIXI(points[0]); + let hole = points[1] ? GeoShape._transformToPIXI(points[1]) : []; + return { polygon, hole } + } + + /** + * Transform an array of poins into an array of PIXI.Points. + * Note: When creating PIXI.Polygons, for some reason the points + * need to be a PIXI.Points object (at least the first one). + * + * @param {array} points - Points in the form of {x:a,y:b} + * @returns An array of PIXI.Points + * @memberof GeoPolygon + */ + static _transformToPIXI(points = []) { + let polygon = []; + points.forEach(point => { + polygon.push(new PIXI.Point(point.x, point.y)); + }); + + return polygon + } + + /** + * Draws a single shape onto the graphics object. + * Useful when being called from subclass. + * + * @protected + * @param {Array.} polygon - An array of PIXI.Points for drawing a polygon. + * @param {Array.} [hole=[]] - An Array of PIXI.Points for cutting a hole into the polygon + * @memberof GeoShape + */ + _drawShape(polygon, hole = []) { + // We save the fill specified in the onDraw event handler. + // + // Consider: Maybe it would be a good idea to add a 'onHoleDraw' + // callback to make the hole customizable. Maybe you want + // to fill it with a different color or an mediocre alpha value. + // then feel free to implement it. + let { fill, alpha } = PIXIUtils.saveFill(this.graphics); + + /** + * This may seem redundant, but it's required + * to make the hole clickable. + * + * It was a bit confusing, so I made a CodePen + * for it: https://codepen.io/Ukmasmu/pen/WJEaoK + */ + if (hole.length > 0) { + this.graphics.beginFill(0x0000ff, 0.0000001); + this.graphics.drawPolygon(hole); + } + + this.graphics.beginFill(fill, alpha); + this.graphics.drawPolygon(polygon); + + if (hole.length > 0) { + this.graphics.beginHole(); + this.graphics.drawPolygon(hole); + this.graphics.endHole(); + } + } + } + + class GeoMultiShape extends GeoShape { + static _manipulatePoints(points, func) { + points.forEach(shape => { + GeoShape._manipulatePoints(shape, func); + }); + } + + static _getPointsFrom(multiShapeArray) { + let points = []; + multiShapeArray.forEach(shape => { + points = points.concat(GeoShape._getPointsFrom(shape)); + }); + return points + } + + static _adaptPoint(coordinates, map) { + let points = []; + coordinates.forEach(shape => { + let adaptedPoint = GeoShape._adaptPoint(shape, map); + points.push(adaptedPoint); + }); + return points + } + + calculateLocation() { + let coms = []; + this.coordinates.forEach(polygon => { + coms.push(GeoGraphics.calculateCenterOfMass(polygon)); + }); + + return GeoGraphics.calculateCenterOfMass(coms) + } + + _drawFrom(multiShape) { + multiShape.forEach(shape => { + super._drawFrom(shape); + }); + } + } + + class PIXIUtils { + /* + * Transform a pixi text to it's actual screensize, + * ignoring it's local transforms + */ + static toScreenFontSize(pixiText, fontSize = null) { + pixiText._recursivePostUpdateTransform(); + + let normalizedScale = { + x: pixiText.scale.x / pixiText.transform.worldTransform.a, + y: pixiText.scale.x / pixiText.transform.worldTransform.d + }; + + pixiText.scale = { x: normalizedScale.x, y: normalizedScale.y }; + if (fontSize) pixiText.style.fontSize = fontSize; + } + + static saveFill(graphics) { + return { + fill: graphics.fill.color, + alpha: graphics.fill.alpha + } + } + } + + //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. + */ + class GeoLayer { + constructor(displayObject) { + 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 { + displayObject.map = this; + this.displayObject = displayObject; + + this.layers = []; + this.geographics = []; + + let pixiAddChild = displayObject.addChild.bind(displayObject); + displayObject.addChild = (...elements) => { + elements.forEach(element => { + if (element instanceof GeoGraphics) { + this.geographics.push(element); + pixiAddChild(element.graphics); + } else { + pixiAddChild(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); + }); + } 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) + // } + + addLayer(layer) { + if (layer instanceof GeoLayer || layer instanceof MapLayer) { + this.layers.push(layer); + 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); + } + + //GeoLayers have to be children of a map layer, + // therefore we can recursively get the map. + get map() { + return this._mapLayer.map + } + + 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 + // } + } + + class MapList { + constructor(active, maps) { + this.maps = maps; + this.active = active; + + if (Object.keys(maps).length > 0) this.select(active); + } + + /** + * Selects a map from the map list. + * + * @param {string} active - Name of the map to select. + * @returns + * @memberof MapList + */ + select(active) { + let map = null; + + if (active !== this.active) { + let keys = Object.keys(this.maps); + if (keys.length > 0) { + if (this.maps[active] == null) { + let altActive = keys[0]; + console.warn( + `The MapList does not contain the provided active key '${active}'. Used '${altActive}' as fallback.` + ); + + active = altActive; + } + + if (this.active !== active) { + this.active = active; + map = this.maps[active]; + } + } else { + console.error(`Could not provide a fallback map! The map object is empty.`); + } + } + + return map + } + + add(key, map) { + if (this.maps[key] != null) consol.warn('Key already in mapList. The existing key was overwritten.'); + this.maps[key] = map; + } + + get map() { + console.log(this.maps, this.active); + return this.maps[this.active] + } + } + + class MapLayer extends GeoLayer { + constructor( + scatterContainer, + displayObject, + { onTransform = null, onChange = null, focus = null, zoom = null, viewport = null } = {} + ) { + super(displayObject); + + let onTransformListeners = [ + () => { + this.labelVisibility(); + } + ]; + if (onTransform) onTransformListeners.push(onTransform); + this.transformHandler = new EventHandler('onTransform', { + listeners: onTransformListeners + }); + + this.scatterContainer = scatterContainer; + + this.changeHandler = new EventHandler('onChange', { + listeners: onChange + }); + + this.mapview = new MapView({ + zoom, + focus, + viewport + }); + + this._map = null; + + // //TODO Implement error handling here. + // this.maps = maps + // if (opts.map) this.placeMap(opts.map) + this.dynamicElements = new Map(); + } + + labelVisibility() { + const visibility = this.childrenVisibility; + + if (visibility) { + const zoom = this.mapview.zoom; + + const min = visibility.min || 0; + const max = visibility.max || Number.MAX_VALUE; + + if (zoom > min && zoom < max) { + this.elements.forEach(it => (it.visible = true)); + + this.elements.forEach(it => { + const scale = 1 / it.parent.scale.x; + + // it.children are poi groups + // it.children[0] is the poi group of the tübingen poi + // it.children[0].children are the text containers (not PIXI.Text), about 20 pieces + + if (it.children.length > 0) { + it.children[0].children.forEach(poi => { + if (poi.children.length === 1) { + poi.scale.set(scale, scale); + } + }); + } + }); + } else { + this.elements.forEach(it => (it.visible = false)); + } + } + } + + 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) + // } + // } + + placeMap(map) { + if (map instanceof GeoMap) { + this.scatterContainer.addChild(map.image); + this.map.onTransform.add(this.transformed.bind(this)); + this.mapview.update(this.map); + + this.adapt(); + } else { + console.error("Could not set map, it's not of type GeoMap.", map); + } + } + + placeElement(elem) { + if (elem instanceof PIXI.DisplayObject) { + this.map.image.addChild(elem); + this.elements.push(elem); + } else { + console.error('Element need to be of type PIXI.DisplayObject.', elem); + } + } + + transformed(e) { + this.mapview.transformed(this.map); + 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 + } + + 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. + ) { + let oldMap = map; + this._map = map; + + if (oldMap) oldMap.unload(); + map.load(); + + this.changeHandler.call(this, map, oldMap); + this.placeMap(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; + /** * The Layers Framework makes managing layers, that may have a different underlying * technology (e.g. PIXI and DOM) more convenient. @@ -18996,7 +20072,9 @@ */ place(elem) { if (elem instanceof Layer) this.placeLayer(elem); - else { + if (elem instanceof GeoLayer) { + this.placeElement(elem.displayObject); + } else { this.placeElement(elem); } } @@ -19488,7 +20566,7 @@ placeElement(pixiElement) { try { pixiElement.layer = this; - this._container.addChild(pixiElement); + this.container.addChild(pixiElement); this.elements.push(pixiElement); return true } catch (e) { @@ -19589,303 +20667,6 @@ } } - //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. - */ - class GeoLayer extends PIXILayer { - constructor(opts = {}) { - super(opts); - - /** - * When setting the map and mapLayer with the options paramter. - * The GeoLayer becomes a RootLayer, when the root layer should not be a MapLayer. - */ - if (opts.map) this._map = opts.map; - if (opts.map) this._mapLayer = opts.mapLayer; - - this.geographics = []; - } - - /** - * 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); - }); - } 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); - } - - placeLayer(layer) { - if (layer instanceof GeoLayer || layer instanceof MapLayer) { - super.placeLayer(layer); - 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); - } - - //GeoLayers have to be children of a map layer, - // therefore we can recursively get the map. - get map() { - return this._map ? this._map : this.parent.map - } - - 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 - } - } - - /** - * TODO: It may be a good idea to inherit maplayer from geo layer. - */ - class MapLayer extends PIXILayer { - constructor(active, maps, scatterContainer, opts = {}) { - super( - Object.assign( - { - container: new PIXI.Container() - }, - opts - ) - ); - - this.opts = opts; - - this.transformHandler = new EventHandler('onTransform', { - listeners: () => { - this.labelVisibility(); - } - }); - - this.scatterContainer = scatterContainer; - - if (!maps[active]) console.error('No map was set!'); - else opts.map = maps[active]; - - this.mapview = new MapView(opts); - this.changeHandler = new EventHandler('onChange', { - listeners: opts.onChange - }); - - //TODO Implement error handling here. - this.maps = maps; - this.changeMap(active); - if (opts.map) this.placeMap(opts.map); - this.dynamicElements = new Map(); - } - - labelVisibility() { - const visibility = this.childrenVisibility; - - if (visibility) { - const zoom = this.mapview.zoom; - - const min = visibility.min || 0; - const max = visibility.max || Number.MAX_VALUE; - - if (zoom > min && zoom < max) { - this.elements.forEach(it => (it.visible = true)); - - this.elements.forEach(it => { - const scale = 1 / it.parent.scale.x; - - // it.children are poi groups - // it.children[0] is the poi group of the tübingen poi - // it.children[0].children are the text containers (not PIXI.Text), about 20 pieces - - if (it.children.length > 0) { - it.children[0].children.forEach(poi => { - if (poi.children.length === 1) { - poi.scale.set(scale, scale); - } - }); - } - }); - } else { - this.elements.forEach(it => (it.visible = false)); - } - } - } - - 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); - } - } - - placeMap(map) { - if (map instanceof GeoMap) { - this.scatterContainer.addChild(map.image); - this.map.onTransform.add(this.transformed.bind(this)); - this.mapview.setMap(this.map); - - this.map.image.addChild(this.container); - - this.adapt(); - } else { - console.error("Could not set map, it's not of type GeoMap.", map); - } - } - - placeElement(elem) { - if (elem instanceof PIXI.DisplayObject) { - this.map.image.addChild(elem); - this.elements.push(elem); - } else { - console.error('Element need to be of type PIXI.DisplayObject.', elem); - } - } - - transformed(e) { - this.mapview.transformed(e); - 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 - } - - changeMap( - key, - { - 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. - } = {} - ) { - Logging$1.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(); - - this.changeHandler.call(this, old); - } 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; - /** * MapApp is responsible for showing fullscreen * map applications. @@ -19918,7 +20699,6 @@ alpha: 0.5 } ); - this.submaps = []; this.overlayElements = new Map(); this.debug = opts.debug; @@ -19926,9 +20706,7 @@ this.showHotkeys = opts.showHotkeys; this.keycodes = this._extractKeyCodes(opts.keycodes); this.coordsLogging = opts.coordsLogging; - this.startmap = opts.startmap; this.overlays = opts.overlays; - this.maps = opts.maps; this.focus = opts.focus; this.zoom = opts.zoom; @@ -19962,9 +20740,24 @@ this.drawMode = this.DRAW_MODES.PIXI_POINT; this.drawData = []; } - this._setupKeyboardUtils(); - Logging$1.log('Application start'); + this.mapList = new MapList(opts.startmap ? opts.startmap : null, opts.maps ? opts.maps : {}); + console.log(this.mapList); + + this._setupKeyboardUtils(); + } + + _setupMapLayer() { + this.mapContainer = new PIXI.Container(); + this.mapLayer = new MapLayer(this.scene, this.mapContainer, { + name: 'Map Layer', + focus: this.focus, + zoom: this.zoom + }); + + this.mapLayer.changeHandler.add(this._mapChanged.bind(this)); + + if (this.mapList.map) this.mapLayer.changeMap(this.mapList.map); } setup() { @@ -19986,26 +20779,8 @@ container: document.body }); - if (!this.startmap) { - let firstMap = Object.keys(this.maps)[0]; - if (firstMap != null) this.startmap = firstMap; - else { - console.error('No map was set. Set a map first, before running the setup command!'); - return - } - } - - //console.log('startup', this.startmap, this.maps) - - this.mapLayer = new MapLayer(this.startmap, this.maps, this.scene, { - name: 'Map Layer', - focus: this.focus, - zoom: this.zoom - }); - - this.mapLayer.changeHandler.add(this._mapChanged.bind(this)); - this.pixiLayer.place(this.mapLayer); - this._mapChanged(null); + this._setupMapLayer(); + this.pixiLayer.place(this.mapContainer); this.pixiUiLayer = new PIXILayer({ name: 'Pixi UI' }); this.pixiLayer.placeLayer(this.pixiUiLayer); @@ -20051,27 +20826,36 @@ }) } - addMaps(maps) { - for (let key in maps) { - this.addMap(key, maps[key]); - } - } - selectMap(key) { - if (this.maps[key]) { - if (this.mapLayer) this.mapLayer.changeMap(key); - } else { - console.error(`Selected map ("${key}") was not (yet) added to the mapapp.`); + this.mapList.select(key); + if (this.mapLayer) { + this.mapLayer.changeMap(this.mapList.map); } } + /** + * Adds and sets a map to the mapapp. + * + * @param {string} key - Name of the map. + * @param {GeoMap} map - Map to add. + * @memberof MapApp + */ setMap(key, map) { this.addMap(key, map); this.selectMap(key); } addMap(key, map) { - this.maps[key] = map; + if (this.mapLayer != null) this.mapLayer.mapList.add(key, map); + if (this.mapList) this.mapList.add(key, map); + //This is necessary as there is a state, when the mapList + else console.error('Cannot access mapLayer. It was not initialized yet.'); + } + + addMaps(mapObject) { + for (let [key, val] of Object.entries(mapObject)) { + this.addMap(key, val); + } } transformed(event) { @@ -20089,14 +20873,16 @@ * Here we guarantee, that the layer order is as it * is defined in the layers. */ - this.pixiLayer.layers.forEach(layer => { - if (layer !== this.mapLayer) { - layer.parent.container.removeChild(layer.container); - layer.parent.container.addChild(layer.container); - } - }); + // this.pixiLayer.layers.forEach(layer => { + // if (layer !== this.mapLayer) { + // layer.parent.container.removeChild(layer.container) + // layer.parent.container.addChild(layer.container) + // } + // }) + console.log(this.map); this.map.onTransform.add(this.transformed.bind(this)); + this.transformed(); this.onMapChanged.call(this, this.map); } @@ -20174,7 +20960,7 @@ } get map() { - return this.mapLayer.map + return this.mapList.map } get activeMapKey() { @@ -20422,708 +21208,6 @@ } } - /* globals PIXI */ - - class FlagType { - static get bottomLeft() { - return { x: 1, y: -1 } - } - static get bottomRight() { - return { x: -1, y: -1 } - } - static get topLeft() { - return { x: -1, y: 1 } - } - static get topRight() { - return { x: 1, y: 1 } - } - - static toString(flagType) { - let str = ''; - if (flagType.x && flagType.y) { - if (flagType.y == 1) str += 'bottom'; - else if (flagType.y == -1) str += 'top'; - else str += '_INVALID_Y_'; - - if (flagType.x == 1) str += 'Right'; - else if (flagType.x == -1) str += 'Left'; - else str += '_INVALID_X_'; - } else str = 'Invalid FlagType: ' + flagType.toString(); - - return str - } - } - - class FlagPolygon extends PIXI.Polygon { - constructor({ - type = FlagType.bottomLeft, - width = 100, - height = 30, - notchSize = 10, - notchWidth = null, - notchHeight = null, - originOffset = { x: 0, y: 0 } - } = {}) { - let points = []; - - let dimensions = { x: width, y: height }; - dimensions = Points.multiply(dimensions, type); - - notchWidth = notchWidth == null ? notchSize : notchWidth; - notchHeight = notchHeight == null ? notchSize : notchHeight; - - notchSize = { x: notchWidth, y: notchHeight }; - notchSize = Points.multiply(notchSize, type); - - originOffset = Points.multiply(originOffset, type); - - let point = new PIXI.Point(originOffset.x, originOffset.y); - points.push(point.clone()); - - point.y += notchSize.y; - points.push(point.clone()); - - point.y += dimensions.y; - points.push(point.clone()); - - point.x += dimensions.x; - points.push(point.clone()); - - point.y -= dimensions.y; - points.push(point.clone()); - - point.x -= dimensions.x - notchSize.x; - points.push(point.clone()); - - // close polygon - points.push(points[0].clone()); - - super(points); - - this.type = type; - this.dimensions = dimensions; - this.notchSize = notchSize; - this.originOffset = originOffset; - } - - getPoint(i) { - if (i >= 0) { - let idx = i * 2; - return [this.points[idx], this.points[idx + 1]] - } else { - let idx = (Math.floor(this.points.length / 2) + i) * 2; - return [this.points[idx], this.points[idx + 1]] - } - } - - get notch() { - let points = [this.getPoint(0), this.getPoint(1), this.getPoint(-2), this.getPoint(0)]; - let notchPolygon = []; - - points.forEach(point => { - notchPolygon = notchPolygon.concat(point); - }); - - return notchPolygon - } - get rect() { - let points = [this.getPoint(1), this.getPoint(2), this.getPoint(3), this.getPoint(4), this.getPoint(1)]; - - let rectPolygon = []; - points.forEach(point => { - rectPolygon = rectPolygon.concat(point); - }); - return rectPolygon - } - - placeText(text, padding) { - text.position = Points.add(this.originOffset, { x: 0, y: this.notchSize.y }); - padding = Points.multiply(padding, this.type); - text.position = Points.add(text.position, padding); - - if (this.type.y == -1) text.position.y -= text.height; - - if (this.type.x == -1) text.position.x -= text.width; - } - } - - class Flag extends PIXI.Graphics { - constructor( - { - type = FlagType.bottomLeft, - width = 100, - height = 30, - notchSize = 10, - notchWidth = null, - notchHeight = null, - originOffset = { x: 0, y: 0 } - } = {}, - nativeLines = false - ) { - super(nativeLines); - - this.flagPolygon = new FlagPolygon({ - type, - width, - height, - notchSize, - notchWidth, - notchHeight, - originOffset - }); - - this.draw(); - } - - draw() { - this.drawPolygon(this.flagPolygon); - } - - get typeName() { - return FlagType.toString(this.type) - } - } - - class Label extends PIXI.Graphics { - constructor(text, textStyle = new PIXI.TextStyle(), nativeLines = false) { - super(nativeLines); - this._text = new PIXI.Text(text, textStyle); - this.addChild(this._text); - } - - get text() { - return this._text - } - } - - /** - * GeoGraphics are graphical objects, that does not store the graphics information - * in screen space, but in geographical coordinates. Therefore GeoGraphics must be - * placed on GeoLayers to work properly. - * - * (Note: As GeoLayers are always children of a map layer. When the map is changed - * all GeoLayers are notified via the 'adaptTo(map)' method.) - * - * The geolayers forward this 'adaptTo' to all children that are GeoGraphics. - * Which adjust their so called 'point' data to the new map. - * - * @abstract - */ - class GeoGraphics { - constructor(coordinates, { scale = 1, onDraw = null, onDrawEnd = null, debug = false } = {}) { - this.coordinates = coordinates; - this.debug = debug; - this.graphics = new PIXI.Graphics(); - this.scale = scale; - this.drawHandler = new EventHandler('onDraw', { listeners: onDraw }); - this.drawEndHandler = new EventHandler('onDrawEnd', { listeners: onDrawEnd }); - this._points = null; - this._position = null; - } - - clone() { - console.error(`Call of abstract method clone(). Overwrite in subclass.`, this); - } - - _cloneOptions() { - return { - debug: this.debug, - scale: this.scale - } - } - - /** - * The _adaptCoordinates is called first by the adaptTo Method. - * Here all coordinates are transformed into point coordinates. - * This must be overloaded in subclass. - * - * @abstract - */ - _adaptCoordinates(map) { - console.error(`Call of abstract method _adaptCoordinates(map). Overwrite in subclass.`, this); - } - - /** - * Gets all screen points in a single array. - * - * @abstract - * @returns {array} - Array of all points in the GeoGraphic. - */ - _getPoints() { - console.error(`Call of abstract method _getPoints(func). Overwrite in subclass.`, this); - } - - /** - * Manipulates all points depending on a function. - * Mainly used to transform points to local space. - * - * @abstract - * @param {function} func - * @memberof GeoGraphics - */ - _manipulatePoints(func) { - console.error(`Call of abstract method _manipulatePoints(func). Overwrite in subclass.`, this); - } - - /** - * The _draw method is called last on adaptation. It creates the GraphicData - * of the specified subclass. To manipulate the style of the graphic, hook an onDraw listener - * to the GeoGraphics object. It is called before the _draw and lets the user modify color and - * lineStyle of the drawn object. - * - * Note: It could also be used for more radical manipulations on the graphics object. - * But this should be used with care. - * - * @abstract - */ - _draw() { - console.error(`Call of abstract method _draw(). Overwrite in subclass.`, this); - } - - /** - * Called by the containing geo layer, when the map changes. - */ - adaptTo(map) { - this._points = this._adaptCoordinates(map); - this._updatePosition(); - this.draw(); - } - - - /** - * Redraws the graphics. - * - * This should be only called if you require an redraw independent of an adapt. - * - * @memberof GeoGraphics - */ - draw() { - this._prepareDraw(); - this.drawHandler.call(this, this.graphics); - this._draw(); - this.drawEndHandler.call(this, this.graphics); - } - - /** - * Retrieves the point data. - * Note: This data changes on adaptation. - */ - get points() { - return this._points - } - - /** - * Returns the screen position of the GeoGraphics. - */ - get position() { - return this._position - } - - get map() { - if ( - this.graphics.layer && - (this.graphics.layer instanceof GeoLayer || this.graphics.layer instanceof MapLayer) - ) { - return this.graphics.layer.map - } else return null - } - - get mapLayer() { - if ( - this.graphics.layer && - (this.graphics.layer instanceof GeoLayer || this.graphics.layer instanceof MapLayer) - ) { - return this.graphics.layer.mapLayer - } else return null - } - - /** - * Prepare draw is a private function, that prepares the graphics - * for the next draw call. It also fires the drawHandler. - * - * @private - * @memberof GeoGraphics - */ - _prepareDraw() { - this.graphics.clear(); - /* - Set a fillcolor and a stroke style for - debugging. Can be overloaded using the onDraw - event function. - */ - if (this.debug) this.graphics.beginFill(0xff00ff); - } - - _updatePosition() { - let points = this._getPoints(); - this._position = GeoGraphics.calculateCenterOfMass(points); - this._manipulatePoints(point => { - point = Points.subtract(point, this._position); - return point - }); - this.graphics.position = this._position; - } - - /** - * Calculates the center of Mass for a set of points. - * - * @static - * @param {Array} points - Array of points in the format {x: a, y:b} - * @returns {object} - Returns a point containing the center of mass of the polygon. - * @memberof GeoGraphics - */ - static calculateCenterOfMass(points) { - let com = new PIXI.Point(); - points.forEach(p => { - let point = new PIXI.Point(p.x, p.y); - com = Points.add(com, point); - }); - return Points.multiplyScalar(com, 1 / points.length) - } - } - - /** - * Represents a single point on the Map. - * - * This GeoGraphics does not provide any visual representation. - * Draw the desired shape in the onDraw callback. - */ - class GeoPoint extends GeoGraphics { - clone() { - return new GeoPoint(this.coordinates, this._cloneOptions()) - } - - _adaptCoordinates(map) { - let scale = 1; - - if (this.mapLayer.map instanceof DeepZoomMap) { - scale = this.mapLayer.map.image.scale.x; - } - - scale = scale / 4; - - return map.coordinatesToPoint(this.coordinates) - } - - _getPoints() { - return [this.points] - } - - _manipulatePoints(func) { - this._points = func(this._points); - } - - _draw() {} - } - - class GeoLine extends GeoGraphics { - /** - * @param {object} opts - Optional values - * @param {array} [opts.points=[]] - Initial points of the geo shape. - * @param {boolean} [closed=false] - Defines if the - */ - constructor(coordinates, { closed = false, size = 1, onDraw = null } = {}) { - super(coordinates, { - size, - onDraw - }); - - this._closed = closed; - } - - clone() { - return new GeoLine(this.coordinates, this._cloneOptions) - } - - _cloneOptions() { - let options = super._cloneOptions(); - Object.assign(options, { - closed: this.closed - }); - } - - /** - * Adds a point to the geo line. - */ - addPoint(coordinate) { - this.coordinates.push(coordinate); - } - - _manipulatePoints(func) { - this.points.forEach((point, idx, array) => { - array[idx] = func(point); - }); - } - - _getPoints() { - return this.points - } - - _adaptCoordinates(map) { - let points = []; - this.coordinates.forEach(point => { - points.push(map.coordinatesToPoint(point)); - }); - return points - } - - _prepareDraw() { - this.graphics.clear(); - if (this.debug) this.graphics.lineStyle(0.5, 0xff00ff); - this.drawHandler.call(this); - } - - _draw() { - /** - * This resets the fill. - * - * DISCUSS: SO: "I'm not sure how the line should be defined. - * On the one hand. The line is clearly intended to - * represent a line and not an area. On the other hand, - * why should the user be prevented from using a fill for the - * area within the line. But if he want's a fill, why don't take - * a Polygon in the first place? - * - * (But if it's a predefined GeoJSON object obtained through e.g. elasticsearch, - * then the user is not in full control of the object type and it may be a good - * addition to grant the user this additional design choice.) - * - * The opportunity to do so would result in additional conditions, when creating the - * GeoGraphics of an overlay." - * */ - this.graphics.beginFill(0, 0); - - if (this.points.length > 0) { - this.graphics.moveTo(this.points[0].x, this.points[0].y); - - for (let i = 1; i < this.points.length; i++) { - this.graphics.lineTo(this.points[i].x, this.points[i].y); - } - - if (this.closed) { - this.graphics.lineTo(this.points[0].x, this.points[0].y); - } - } - } - - get closed() { - return this._closed - } - - set closed(val) { - if (val != this._closed) { - this._closed = val; - this.draw(); - } - } - } - - class GeoShape extends GeoGraphics { - clone() { - return new GeoShape(this.coordinates, this._cloneOptions) - } - - _manipulatePoints(func) { - this.constructor._manipulatePoints(this.points, func); - } - - /** - * Mets the requirements for the _manipulatePointsMethod(). - * Primarily used in subclasses to get the processing steps from - * their superclass. - * - * @static - * @protected - * @param {Array.} points - The points array that shold be manipulated. - * @param {function} func - The function that changes the single point value. Has to return a new point. - * @memberof GeoShape - */ - static _manipulatePoints(points, func) { - points.forEach((pointArray, arrIdx) => { - pointArray.forEach((point, idx) => { - points[arrIdx][idx] = func(point); - }); - }); - } - - _getPoints() { - return this.constructor._getPointsFrom(this.points) - } - - /** - * Returns all points of a polygon array. - * Useful for when getting sub polygons in child class. - */ - static _getPointsFrom(shape) { - let concatArray = []; - shape.forEach(array => { - concatArray = concatArray.concat(array); - }); - - return concatArray - } - - _adaptCoordinates(map) { - let val = this.constructor._adaptPoint(this.coordinates, map); - return val - } - - static _adaptPoint(coordinates, map) { - return coordinates.map(array => { - return array.map(point => { - return map.coordinatesToPoint(point) - }) - }) - } - - _draw() { - this._drawFrom(this.points); - this.graphics.position = this.position; - } - - _drawFrom(shape) { - const { polygon, hole } = this.constructor._pointsToShape(shape); - this._drawShape(polygon, hole); - } - - /** - * Separates the points array into it's two parts: - * - the solid polygon - * - a hole that is cut into the polygon (optional) - * - * @private - * @returns {object} - Returns an object containing the polygon and the hole in the form of {polygon: [...PIXI.Point], hole: [...PIXI.Point]} - */ - static _pointsToShape(points) { - let polygon = GeoShape._transformToPIXI(points[0]); - let hole = points[1] ? GeoShape._transformToPIXI(points[1]) : []; - return { polygon, hole } - } - - /** - * Transform an array of poins into an array of PIXI.Points. - * Note: When creating PIXI.Polygons, for some reason the points - * need to be a PIXI.Points object (at least the first one). - * - * @param {array} points - Points in the form of {x:a,y:b} - * @returns An array of PIXI.Points - * @memberof GeoPolygon - */ - static _transformToPIXI(points = []) { - let polygon = []; - points.forEach(point => { - polygon.push(new PIXI.Point(point.x, point.y)); - }); - - return polygon - } - - /** - * Draws a single shape onto the graphics object. - * Useful when being called from subclass. - * - * @protected - * @param {Array.} polygon - An array of PIXI.Points for drawing a polygon. - * @param {Array.} [hole=[]] - An Array of PIXI.Points for cutting a hole into the polygon - * @memberof GeoShape - */ - _drawShape(polygon, hole = []) { - // We save the fill specified in the onDraw event handler. - // - // Consider: Maybe it would be a good idea to add a 'onHoleDraw' - // callback to make the hole customizable. Maybe you want - // to fill it with a different color or an mediocre alpha value. - // then feel free to implement it. - let { fill, alpha } = PIXIUtils.saveFill(this.graphics); - - /** - * This may seem redundant, but it's required - * to make the hole clickable. - * - * It was a bit confusing, so I made a CodePen - * for it: https://codepen.io/Ukmasmu/pen/WJEaoK - */ - if (hole.length > 0) { - this.graphics.beginFill(0x0000ff, 0.0000001); - this.graphics.drawPolygon(hole); - } - - this.graphics.beginFill(fill, alpha); - this.graphics.drawPolygon(polygon); - - if (hole.length > 0) { - this.graphics.beginHole(); - this.graphics.drawPolygon(hole); - this.graphics.endHole(); - } - } - } - - class GeoMultiShape extends GeoShape { - static _manipulatePoints(points, func) { - points.forEach(shape => { - GeoShape._manipulatePoints(shape, func); - }); - } - - static _getPointsFrom(multiShapeArray) { - let points = []; - multiShapeArray.forEach(shape => { - points = points.concat(GeoShape._getPointsFrom(shape)); - }); - return points - } - - static _adaptPoint(coordinates, map) { - let points = []; - coordinates.forEach(shape => { - let adaptedPoint = GeoShape._adaptPoint(shape, map); - points.push(adaptedPoint); - }); - return points - } - - calculateLocation() { - let coms = []; - this.coordinates.forEach(polygon => { - coms.push(GeoGraphics.calculateCenterOfMass(polygon)); - }); - - return GeoGraphics.calculateCenterOfMass(coms) - } - - _drawFrom(multiShape) { - multiShape.forEach(shape => { - super._drawFrom(shape); - }); - } - } - - class PIXIUtils { - /* - * Transform a pixi text to it's actual screensize, - * ignoring it's local transforms - */ - static toScreenFontSize(pixiText, fontSize = null) { - pixiText._recursivePostUpdateTransform(); - - let normalizedScale = { - x: pixiText.scale.x / pixiText.transform.worldTransform.a, - y: pixiText.scale.x / pixiText.transform.worldTransform.d - }; - - pixiText.scale = { x: normalizedScale.x, y: normalizedScale.y }; - if (fontSize) pixiText.style.fontSize = fontSize; - } - - static saveFill(graphics) { - return { - fill: graphics.fill.color, - alpha: graphics.fill.alpha - } - } - } - /** * Helper class for handling GeoJson data. * As specified by [RFC7946](https://tools.ietf.org/html/rfc7946). @@ -21644,7 +21728,7 @@ item.overlay = this; let graphics = this.createItem(item, informationCallback); - geoLayer.place(graphics); + geoLayer.addChild(graphics); } }); return geoLayer @@ -22075,6 +22159,7 @@ window.CoverScatter = CoverScatter; window.GeoLayer = GeoLayer; window.MapLayer = MapLayer; + window.MapList = MapList; window.GeoGraphics = GeoGraphics; window.GeoPoint = GeoPoint; diff --git a/lib/pixi/bundle.js b/lib/pixi/bundle.js index 3b56d52..ad2861f 100755 --- a/lib/pixi/bundle.js +++ b/lib/pixi/bundle.js @@ -93,9 +93,10 @@ window.RigidContainer = RigidContainer window.CompactScatter = CompactScatter window.CoverScatter = CoverScatter -import { GeoLayer, MapLayer } from './maps/geolayer.js' +import { GeoLayer, MapLayer, MapList } from './maps/geolayer.js' window.GeoLayer = GeoLayer window.MapLayer = MapLayer +window.MapList = MapList import { GeoGraphics, GeoPoint, GeoLine, GeoShape, GeoMultiShape } from './maps/geographics.js' @@ -105,7 +106,6 @@ window.GeoLine = GeoLine window.GeoShape = GeoShape window.GeoMultiShape = GeoMultiShape - -import Overlay from "./maps/overlay.js" +import Overlay from './maps/overlay.js' window.Overlay = Overlay diff --git a/lib/pixi/maps/geographics.html b/lib/pixi/maps/geographics.html index a0f5df7..5c44ebf 100644 --- a/lib/pixi/maps/geographics.html +++ b/lib/pixi/maps/geographics.html @@ -38,6 +38,10 @@ tokio: { x: 35.696278, y: 139.731366 } } + window.apps = [ + + ] + function createApp(view) { let app = new MapApp({ view, @@ -52,6 +56,7 @@ const wikimedia = "../assets/maps/wikimedia-world-robinson/2000px-BlankMap-World.png" + return new Promise((resolve, reject) => { setTimeout(() => { reject("Creating app timed out.") @@ -69,13 +74,12 @@ cover: false }) - console.log(wikimediaMap) - app.addMaps({ "osm": osmMap, "wiki": wikimediaMap }) app.selectMap("osm") app.setup().run() + window.apps.push(app) resolve(app) }, { resolutionDependent: false }) @@ -127,7 +131,9 @@ ; (function () { createApp(geopoint_canvas).then(app => { - let capitalLayer = new GeoLayer({ name: "Capital Overlay" }) + + let capitalContainer = new PIXI.Container() + let capitalLayer = new GeoLayer(capitalContainer) for (key in capitals) { let capitalPoint = new GeoPoint(capitals[key], { @@ -145,18 +151,20 @@ this.graphics.endFill() } }) - capitalLayer.place(capitalPoint) + capitalContainer.addChild(capitalPoint) } enableSwitch(geopoint_switch, app) - app.mapLayer.place(capitalLayer) + // app.mapLayer.place(capitalLayer) + + app.mapLayer.addLayer(capitalLayer) }).catch(console.error) })() -
+ - \ No newline at end of file + \ No newline at end of file diff --git a/lib/pixi/maps/geographics.js b/lib/pixi/maps/geographics.js index cddfab7..db9703e 100644 --- a/lib/pixi/maps/geographics.js +++ b/lib/pixi/maps/geographics.js @@ -1,7 +1,6 @@ import { Points } from '../../utils.js' import { EventHandler } from './utils.js' import { FlagPolygon } from '../graphics/label.js' -import { GeoLayer, MapLayer } from './geolayer.js' import { DeepZoomMap } from './map.js' /** @@ -97,10 +96,9 @@ export class GeoGraphics { this.draw() } - /** * Redraws the graphics. - * + * * This should be only called if you require an redraw independent of an adapt. * * @memberof GeoGraphics @@ -127,23 +125,23 @@ export class GeoGraphics { return this._position } - get map() { - if ( - this.graphics.layer && - (this.graphics.layer instanceof GeoLayer || this.graphics.layer instanceof MapLayer) - ) { - return this.graphics.layer.map - } else return null - } + // get map() { + // if ( + // this.graphics.layer && + // (this.graphics.layer instanceof GeoLayer || this.graphics.layer instanceof MapLayer) + // ) { + // return this.graphics.layer.map + // } else return null + // } - get mapLayer() { - if ( - this.graphics.layer && - (this.graphics.layer instanceof GeoLayer || this.graphics.layer instanceof MapLayer) - ) { - return this.graphics.layer.mapLayer - } else return null - } + // get mapLayer() { + // if ( + // this.graphics.layer && + // (this.graphics.layer instanceof GeoLayer || this.graphics.layer instanceof MapLayer) + // ) { + // return this.graphics.layer.mapLayer + // } else return null + // } /** * Prepare draw is a private function, that prepares the graphics @@ -204,8 +202,8 @@ export class GeoPoint extends GeoGraphics { _adaptCoordinates(map) { let scale = 1 - if (this.mapLayer.map instanceof DeepZoomMap) { - scale = this.mapLayer.map.image.scale.x + if (map instanceof DeepZoomMap) { + scale = map.image.scale.x } scale = scale / 4 diff --git a/lib/pixi/maps/geolayer.js b/lib/pixi/maps/geolayer.js index d2c58d2..c4a3d35 100644 --- a/lib/pixi/maps/geolayer.js +++ b/lib/pixi/maps/geolayer.js @@ -4,6 +4,7 @@ import { EventHandler } from './utils.js' import { PIXILayer } from '../../../../src/layers/js/layer.js' import Logging from '../../logging.js' +import { GeoGraphics } from './geographics.js' //import { GeoGraphics } from "../pixi/geographics.js" /** @@ -11,18 +12,33 @@ import Logging from '../../logging.js' * GeoGraphics. The layer can be adapted to a map and notifies all Geo-Children * of the Adaption. */ -export class GeoLayer extends PIXILayer { - constructor(opts = {}) { - super(opts) +export class GeoLayer { + constructor(displayObject) { + 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 { + displayObject.map = this + this.displayObject = displayObject - /** - * When setting the map and mapLayer with the options paramter. - * The GeoLayer becomes a RootLayer, when the root layer should not be a MapLayer. - */ - if (opts.map) this._map = opts.map - if (opts.map) this._mapLayer = opts.mapLayer + this.layers = [] + this.geographics = [] - this.geographics = [] + let pixiAddChild = displayObject.addChild.bind(displayObject) + displayObject.addChild = (...elements) => { + elements.forEach(element => { + if (element instanceof GeoGraphics) { + this.geographics.push(element) + pixiAddChild(element.graphics) + } else { + pixiAddChild(element) + } + }) + } + } } /** @@ -43,18 +59,19 @@ export class GeoLayer extends PIXILayer { } 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) - } + // 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) + // } - placeLayer(layer) { + addLayer(layer) { if (layer instanceof GeoLayer || layer instanceof MapLayer) { - super.placeLayer(layer) + this.layers.push(layer) + 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) @@ -63,74 +80,126 @@ export class GeoLayer extends PIXILayer { //GeoLayers have to be children of a map layer, // therefore we can recursively get the map. get map() { - return this._map ? this._map : this.parent.map + return this._mapLayer.map } get mapLayer() { return this._mapLayer ? this._mapLayer : this.parent.mapLayer } - clone(mapLayerClone) { - const opts = { - mapLayer: mapLayerClone, - map: mapLayerClone.map + // 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 MapList { + constructor(active, maps) { + this.maps = maps + this.active = active + + if (Object.keys(maps).length > 0) this.select(active) + } + + /** + * Selects a map from the map list. + * + * @param {string} active - Name of the map to select. + * @returns + * @memberof MapList + */ + select(active) { + let map = null + + if (active !== this.active) { + let keys = Object.keys(this.maps) + if (keys.length > 0) { + if (this.maps[active] == null) { + let altActive = keys[0] + console.warn( + `The MapList does not contain the provided active key '${active}'. Used '${altActive}' as fallback.` + ) + + active = altActive + } + + if (this.active !== active) { + this.active = active + map = this.maps[active] + } + } else { + console.error(`Could not provide a fallback map! The map object is empty.`) + } } - let geoLayerClone = new GeoLayer(opts) - this.layers.forEach(layer => { - let layerClone = layer.clone(opts) - if (layerClone) { - geoLayerClone.placeLayer(layerClone) - } - }) + return map + } - this.geographics.forEach(geographics => { - let clone = geographics.clone() - if (clone) { - geoLayerClone.place(clone) - } - }) + add(key, map) { + if (this.maps[key] != null) consol.warn('Key already in mapList. The existing key was overwritten.') + this.maps[key] = map + } - return geoLayerClone + get map() { + console.log(this.maps, this.active) + return this.maps[this.active] } } -/** - * TODO: It may be a good idea to inherit maplayer from geo layer. - */ -export class MapLayer extends PIXILayer { - constructor(active, maps, scatterContainer, opts = {}) { - super( - Object.assign( - { - container: new PIXI.Container() - }, - opts - ) - ) +export class MapLayer extends GeoLayer { + constructor( + scatterContainer, + displayObject, + { onTransform = null, onChange = null, focus = null, zoom = null, viewport = null } = {} + ) { + super(displayObject) - this.opts = opts - - this.transformHandler = new EventHandler('onTransform', { - listeners: () => { + let onTransformListeners = [ + () => { this.labelVisibility() } + ] + if (onTransform) onTransformListeners.push(onTransform) + this.transformHandler = new EventHandler('onTransform', { + listeners: onTransformListeners }) this.scatterContainer = scatterContainer - if (!maps[active]) console.error('No map was set!') - else opts.map = maps[active] - - this.mapview = new MapView(opts) this.changeHandler = new EventHandler('onChange', { - listeners: opts.onChange + listeners: onChange }) - //TODO Implement error handling here. - this.maps = maps - this.changeMap(active) - if (opts.map) this.placeMap(opts.map) + this.mapview = new MapView({ + zoom, + focus, + viewport + }) + + this._map = null + + // //TODO Implement error handling here. + // this.maps = maps + // if (opts.map) this.placeMap(opts.map) this.dynamicElements = new Map() } @@ -173,20 +242,18 @@ export class MapLayer extends PIXILayer { }) } - placeLayer(layer) { - super.placeLayer(layer) - if (layer instanceof GeoLayer && this.map) { - layer.adapt(this.map) - } - } + // placeLayer(layer) { + // super.placeLayer(layer) + // if (layer instanceof GeoLayer && this.map) { + // layer.adapt(this.map) + // } + // } placeMap(map) { if (map instanceof GeoMap) { this.scatterContainer.addChild(map.image) this.map.onTransform.add(this.transformed.bind(this)) - this.mapview.setMap(this.map) - - this.map.image.addChild(this.container) + this.mapview.update(this.map) this.adapt() } else { @@ -204,7 +271,7 @@ export class MapLayer extends PIXILayer { } transformed(e) { - this.mapview.transformed(e) + this.mapview.transformed(this.map) this.transformHandler.call(this) } @@ -228,12 +295,19 @@ export class MapLayer extends PIXILayer { } changeMap( - key, - { - 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. - } = {} + 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. ) { - Logging.log(`Map change: ${key}`) + let oldMap = map + this._map = map + + if (oldMap) oldMap.unload() + map.load() + + this.changeHandler.call(this, map, oldMap) + this.placeMap(map) + + /*Logging.log(`Map change: ${key}`) if (this.active !== key) { if (this.maps.hasOwnProperty(key)) { @@ -265,9 +339,10 @@ export class MapLayer extends PIXILayer { * * -SO */ + /* if (old) old.unload() - this.changeHandler.call(this, old) + } else { let keys = Object.keys(this.maps) @@ -282,7 +357,7 @@ export class MapLayer extends PIXILayer { }) } } - } + }*/ } get map() { @@ -297,6 +372,8 @@ export class MapLayer extends PIXILayer { get mapLayer() { return this } + + } MapLayer.idx = 0 diff --git a/lib/pixi/maps/map.js b/lib/pixi/maps/map.js index 90486d3..29d5b07 100644 --- a/lib/pixi/maps/map.js +++ b/lib/pixi/maps/map.js @@ -130,8 +130,10 @@ export class GeoMap { } unload() { - this.image.parent.removeChild(this.image) - this.image.scatter = null + if (this.image) { + if (this.image.parent) this.image.parent.removeChild(this.image) + this.image.scatter = null + } } /** @@ -181,6 +183,17 @@ export class GeoMap { this.image.scatter = scatter == null ? this.scatter : scatter this.onLoad.call(this) + + // Object.assign(this.image, { + // set scatter (value) { + // console.trace("Scatter set.") + // this._scatter = value + // }, + // get scatter (){ + // console.trace("Get Scatter.") + // return this._scatter + // } + // }) } /** @@ -264,21 +277,6 @@ export class GeoMap { return _point } - // /** - // * Appends the object to a PIXI container. This is important, - // * to notify the map, that it's parent has changed. - // * - // * If you want to use PIXI's addChild, make sure you call - // * appended right afterwards. - // * - // * @param {PIXI.Container} container - // * @returns Returns the map object to allow chaining. - // */ - // appendTo(container) { - // container.addChild(this.image) - // return this.appended(container) - // } - get width() { return this.image.scatter.width / this.image.scatter.scale } @@ -498,6 +496,8 @@ export class GeoMap { return null } } + + } GeoMap.counter = 0 @@ -763,6 +763,7 @@ export class ImageMap extends GeoMap { load(container = null, scatter = null) { super.load(this.sprite, container, scatter) + console.log('LOADED') this.image.alpha = this.alpha this.image.interactive = true } diff --git a/lib/pixi/maps/mapapp.html b/lib/pixi/maps/mapapp.html index 7754845..b261783 100644 --- a/lib/pixi/maps/mapapp.html +++ b/lib/pixi/maps/mapapp.html @@ -83,8 +83,6 @@ // Finally apply the map to the MapApp app.setMap('europe', imageMap) - console.error("IMAGE MAP SET!") - // The app requires a map before beeing able to run. // So start the app here. diff --git a/lib/pixi/maps/mapapp.js b/lib/pixi/maps/mapapp.js index 6102923..9186f9b 100644 --- a/lib/pixi/maps/mapapp.js +++ b/lib/pixi/maps/mapapp.js @@ -1,7 +1,7 @@ import PIXIApp from '../app.js' import { CoordinateDisplay } from '../../../../js/display.js' import { DOMLayer, PIXILayer } from '../../../../src/layers/js/layer.js' -import { MapLayer } from './geolayer.js' +import { MapLayer, MapList } from './geolayer.js' import { RigidScatterContainer } from './scatter.js' import { EventHandler } from './utils.js' import { Points } from '../../utils.js' @@ -39,7 +39,6 @@ export default class MapApp extends PIXIApp { alpha: 0.5 } ) - this.submaps = [] this.overlayElements = new Map() this.debug = opts.debug @@ -47,9 +46,7 @@ export default class MapApp extends PIXIApp { this.showHotkeys = opts.showHotkeys this.keycodes = this._extractKeyCodes(opts.keycodes) this.coordsLogging = opts.coordsLogging - this.startmap = opts.startmap this.overlays = opts.overlays - this.maps = opts.maps this.focus = opts.focus this.zoom = opts.zoom @@ -83,9 +80,24 @@ export default class MapApp extends PIXIApp { this.drawMode = this.DRAW_MODES.PIXI_POINT this.drawData = [] } - this._setupKeyboardUtils() - Logging.log('Application start') + this.mapList = new MapList(opts.startmap ? opts.startmap : null, opts.maps ? opts.maps : {}) + console.log(this.mapList) + + this._setupKeyboardUtils() + } + + _setupMapLayer() { + this.mapContainer = new PIXI.Container() + this.mapLayer = new MapLayer(this.scene, this.mapContainer, { + name: 'Map Layer', + focus: this.focus, + zoom: this.zoom + }) + + this.mapLayer.changeHandler.add(this._mapChanged.bind(this)) + + if (this.mapList.map) this.mapLayer.changeMap(this.mapList.map) } setup() { @@ -107,26 +119,8 @@ export default class MapApp extends PIXIApp { container: document.body }) - if (!this.startmap) { - let firstMap = Object.keys(this.maps)[0] - if (firstMap != null) this.startmap = firstMap - else { - console.error('No map was set. Set a map first, before running the setup command!') - return - } - } - - //console.log('startup', this.startmap, this.maps) - - this.mapLayer = new MapLayer(this.startmap, this.maps, this.scene, { - name: 'Map Layer', - focus: this.focus, - zoom: this.zoom - }) - - this.mapLayer.changeHandler.add(this._mapChanged.bind(this)) - this.pixiLayer.place(this.mapLayer) - this._mapChanged(null) + this._setupMapLayer() + this.pixiLayer.place(this.mapContainer) this.pixiUiLayer = new PIXILayer({ name: 'Pixi UI' }) this.pixiLayer.placeLayer(this.pixiUiLayer) @@ -172,27 +166,36 @@ export default class MapApp extends PIXIApp { }) } - addMaps(maps) { - for (let key in maps) { - this.addMap(key, maps[key]) - } - } - selectMap(key) { - if (this.maps[key]) { - if (this.mapLayer) this.mapLayer.changeMap(key) - } else { - console.error(`Selected map ("${key}") was not (yet) added to the mapapp.`) + this.mapList.select(key) + if (this.mapLayer) { + this.mapLayer.changeMap(this.mapList.map) } } + /** + * Adds and sets a map to the mapapp. + * + * @param {string} key - Name of the map. + * @param {GeoMap} map - Map to add. + * @memberof MapApp + */ setMap(key, map) { this.addMap(key, map) this.selectMap(key) } addMap(key, map) { - this.maps[key] = map + if (this.mapLayer != null) this.mapLayer.mapList.add(key, map) + if (this.mapList) this.mapList.add(key, map) + //This is necessary as there is a state, when the mapList + else console.error('Cannot access mapLayer. It was not initialized yet.') + } + + addMaps(mapObject) { + for (let [key, val] of Object.entries(mapObject)) { + this.addMap(key, val) + } } transformed(event) { @@ -210,14 +213,16 @@ export default class MapApp extends PIXIApp { * Here we guarantee, that the layer order is as it * is defined in the layers. */ - this.pixiLayer.layers.forEach(layer => { - if (layer !== this.mapLayer) { - layer.parent.container.removeChild(layer.container) - layer.parent.container.addChild(layer.container) - } - }) + // this.pixiLayer.layers.forEach(layer => { + // if (layer !== this.mapLayer) { + // layer.parent.container.removeChild(layer.container) + // layer.parent.container.addChild(layer.container) + // } + // }) + console.log(this.map) this.map.onTransform.add(this.transformed.bind(this)) + this.transformed() this.onMapChanged.call(this, this.map) } @@ -295,7 +300,7 @@ export default class MapApp extends PIXIApp { } get map() { - return this.mapLayer.map + return this.mapList.map } get activeMapKey() { diff --git a/lib/pixi/maps/mapview.js b/lib/pixi/maps/mapview.js index 40eead1..3ebd9f4 100644 --- a/lib/pixi/maps/mapview.js +++ b/lib/pixi/maps/mapview.js @@ -13,16 +13,10 @@ export default class MapView { * @param {number} [zoom = 0] - Defines the startup zoom of the app. Note that this is just a request. * The MapView will prioritize a full scale app, than displaying the demanded zoom factor */ - constructor({ - map = null, - focus = null, - zoom = null, - viewport = { min: { x: -85, y: -180 }, max: { x: 85, y: 180 } } - } = {}) { + constructor({ focus = null, zoom = null, viewport = { min: { x: -85, y: -180 }, max: { x: 85, y: 180 } } } = {}) { this.viewport = viewport this._focus = focus this._zoom = zoom - this._map = map this.referenceHeight = 256 } @@ -33,66 +27,53 @@ export default class MapView { return this._zoom } - get map() { - return this._map + update(map) { + map.moveTo(this._focus, this._zoom) } - /** - * Sets the map to the given focuspoint and zoom factor. - */ - setMap(map) { - this._map = map - this.update() - return true + transformed(map) { + this.updateZoom(map) + this.updateFocusPoint(map) } - update() { - this.map.moveTo(this._focus, this._zoom) + applyCameraPosition(map) { + this.updateFocusPoint(map) + this.updateZoom(map) } - transformed(e) { - this.updateZoom() - this.updateFocusPoint() + updateFocusPoint(map) { + const frame = map.getFrame() + this._focus = this.coordinatesFromWindowPoint(map, frame.localCenter) } - applyCameraPosition() { - this.updateFocusPoint() - this.updateZoom() - } - - updateFocusPoint() { - const frame = this.map.getFrame() - this._focus = this.coordinatesFromWindowPoint(frame.localCenter) - } - - updateZoom() { + updateZoom(map) { /** * TODO: This relies on the fact, that all maps have the same tileSize, * if a set would have a smaller tileSize. Improve that. */ - if (this.map instanceof DeepZoomMap) this._zoom = this.map.floatingLevelForScale(this.map.image.scatter.scale) + if (map instanceof DeepZoomMap) this._zoom = map.floatingLevelForScale(map.image.scatter.scale) else { - this._zoom = this.map.zoom - console.warn('Zoom is not yet correctly implemented in this Map type: ' + this.map) + this._zoom = map.zoom + console.warn('Zoom is not yet correctly implemented in this Map type: ' + map) } } - mapPointToWindowPoint(point) { - let container = this.map.image.parent + mapPointToWindowPoint(map, point) { + let container = map.image.parent let _point = new PIXI.Point( - this.map.scatter.position.x + this.map.scatter.scale * point.x, - this.map.scatter.position.y + this.map.scatter.scale * point.y + map.scatter.position.x + map.scatter.scale * point.x, + map.scatter.position.y + map.scatter.scale * point.y ) return container.toGlobal(_point) } - windowPointToMapPoint(point) { - let offset = this.map.image.parent.toGlobal({ x: 0, y: 0 }) + windowPointToMapPoint(map, point) { + let offset = map.image.parent.toGlobal({ x: 0, y: 0 }) let _point = new PIXI.Point( - (point.x - this.map.scatter.position.x - offset.x) / this.map.scatter.scale, - (point.y - this.map.scatter.position.y - offset.y) / this.map.scatter.scale + (point.x - map.scatter.position.x - offset.x) / map.scatter.scale, + (point.y - map.scatter.position.y - offset.y) / map.scatter.scale ) return _point @@ -105,18 +86,18 @@ export default class MapView { * @returns {{x,y}} Coordinates on the map of the provided position. * @memberof MapView */ - coordinatesFromWindowPoint(point) { + coordinatesFromWindowPoint(map, point) { let position = { - x: point.x - this.map.scatter.position.x, - y: point.y - this.map.scatter.position.y + x: point.x - map.scatter.position.x, + y: point.y - map.scatter.position.y } let normalized = { - x: position.x / (this.map.width * this.map.scatter.scale), - y: position.y / (this.map.height * this.map.scatter.scale) + x: position.x / (map.width * map.scatter.scale), + y: position.y / (map.height * map.scatter.scale) } - let coordinates = this.map.mapdata.toCoordinates(normalized) + let coordinates = map.mapdata.toCoordinates(normalized) return coordinates } diff --git a/lib/pixi/maps/overlay.js b/lib/pixi/maps/overlay.js index 5253990..3cff470 100644 --- a/lib/pixi/maps/overlay.js +++ b/lib/pixi/maps/overlay.js @@ -196,7 +196,7 @@ export default class Overlay { item.overlay = this let graphics = this.createItem(item, informationCallback) - geoLayer.place(graphics) + geoLayer.addChild(graphics) } }) return geoLayer diff --git a/lib/pixi/maps/scatter.js b/lib/pixi/maps/scatter.js index fdc6900..99359cc 100644 --- a/lib/pixi/maps/scatter.js +++ b/lib/pixi/maps/scatter.js @@ -526,13 +526,14 @@ export class MapObjectScatter extends CoverScatter { }, opts ) + super(displayObject, renderer, opts) + if (!renderer) { console.error('Renderer was not set!') return } - super(displayObject, renderer, opts) this.cover = opts.cover }