commited for presentation.

This commit is contained in:
Severin Opel 2019-12-11 15:29:59 +01:00
parent 65fac2f406
commit a85569e54d
120 changed files with 81240 additions and 622 deletions

View File

@ -73,12 +73,12 @@ div.wrapper {
/*** CSS taken from https://medium.com/@jamesfuthey/simulating-the-creation-of-website-thumbnail-screenshots-using-iframes-7145269891db#.7v7fshos5 ***/ /*** CSS taken from https://medium.com/@jamesfuthey/simulating-the-creation-of-website-thumbnail-screenshots-using-iframes-7145269891db#.7v7fshos5 ***/
.thumbnail { .thumbnail {
position: relative; position: relative;
-ms-zoom: 0.25; -ms-zoom: 0.5;
-moz-transform: scale(0.25); -moz-transform: scale(0.5);
-moz-transform-origin: 0 0; -moz-transform-origin: 0 0;
-o-transform: scale(0.25); -o-transform: scale(0.5);
-o-transform-origin: 0 0; -o-transform-origin: 0 0;
-webkit-transform: scale(0.25); -webkit-transform: scale(0.5);
-webkit-transform-origin: 0 0; -webkit-transform-origin: 0 0;
} }
@ -110,8 +110,8 @@ iframe {
} }
.thumbnail-container { .thumbnail-container {
width: calc(1024px * 0.25); width: calc(1024px * 0.5);
height: calc(624px * 0.25); height: calc(624px * 0.5);
display: inline-block; display: inline-block;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
@ -126,8 +126,8 @@ div.preview {
color: #333; color: #333;
font-size: 12pt; font-size: 12pt;
text-align: center; text-align: center;
width: 256px; /* width: 256px;
height: 196px; height: 196px; */
} }
div.title { div.title {

View File

@ -36,7 +36,21 @@
"./lib/pixi/switch.js", "./lib/pixi/switch.js",
"./lib/pixi/theme.js", "./lib/pixi/theme.js",
"./lib/pixi/tooltip.js", "./lib/pixi/tooltip.js",
"./lib/pixi/volatile.js" "./lib/pixi/volatile.js",
"./lib/pixi/maps/mapapp.js",
"./lib/pixi/maps/mapviewport.js",
"./lib/pixi/maps/geographics.js",
"./lib/pixi/maps/geojson.js",
"./lib/pixi/maps/geolayer.js",
"./lib/pixi/maps/mapdata.js",
"./lib/pixi/maps/maplist.js",
"./lib/pixi/maps/overlay.js",
"./lib/pixi/maps/scatter.js",
"./lib/pixi/maps/utils.js",
"./lib/pixi/maps/map.js",
"./lib/pixi/maps/projections/projection.js",
"./lib/pixi/maps/projections/mercator.js",
"./lib/pixi/maps/projections/robinson.js"
], ],
"exclude": [], "exclude": [],
"includePattern": ".+\\.js(doc|x)?$", "includePattern": ".+\\.js(doc|x)?$",
@ -45,7 +59,10 @@
"sourceType": "module", "sourceType": "module",
"tags": { "tags": {
"allowUnknownTags": true, "allowUnknownTags": true,
"dictionaries": ["jsdoc", "closure"] "dictionaries": [
"jsdoc",
"closure"
]
}, },
"templates": { "templates": {
"applicationName": "iwmlib", "applicationName": "iwmlib",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -181,6 +181,9 @@
<!--GeoPoint --> <!--GeoPoint -->
<section id="geopoint"> <section id="geopoint">
<h2>GeoPoint</h2> <h2>GeoPoint</h2>
<a href="../../../doc/out/GeoPoint.html" class="documentation" target="_blank"><i class="material-icons">
book
</i> GeoPoint</a>
<p>GeoPoint is a single coordinate in the map.</p> <p>GeoPoint is a single coordinate in the map.</p>
<canvas id="geopoint_canvas"></canvas> <canvas id="geopoint_canvas"></canvas>
<div class=" controls"> <div class=" controls">

View File

@ -5,7 +5,7 @@ import { DeepZoomMap } from './map.js'
import { PIXIUtils } from '../../../../js/pixi/utils.js' import { PIXIUtils } from '../../../../js/pixi/utils.js'
/** /**
* GeoGraphics are graphical objects, that does not store the graphics information * * GeoGraphics are graphical objects, that does not store the graphics information
* in screen space, but in geographical coordinates. Therefore GeoGraphics must be * in screen space, but in geographical coordinates. Therefore GeoGraphics must be
* placed on GeoLayers to work properly. * placed on GeoLayers to work properly.
* *
@ -15,11 +15,12 @@ import { PIXIUtils } from '../../../../js/pixi/utils.js'
* The geolayers forward this 'adaptTo' to all children that are GeoGraphics. * The geolayers forward this 'adaptTo' to all children that are GeoGraphics.
* Which adjust their so called 'point' data to the new map. * Which adjust their so called 'point' data to the new map.
* *
* @abstract * @export
* @class GeoGraphics
*/ */
export class GeoGraphics { export class GeoGraphics {
constructor(coordinates, { scale = 1, onDraw = null, onDrawEnd = null, debug = false } = {}) { constructor(coordinates, { scale = 1, onDraw = null, onDrawEnd = null, debug = false } = {}) {
this.coordinates = coordinates this._coordinates = coordinates
this.debug = debug this.debug = debug
this.graphics = new PIXI.Graphics() this.graphics = new PIXI.Graphics()
this.scale = scale this.scale = scale
@ -29,6 +30,17 @@ export class GeoGraphics {
this._position = null this._position = null
} }
/**
* The coordinates of the geographics.
*
* @member {array}
* @readonly
* @memberof GeoGraphics
*/
get coordinates() {
return this._coordinates
}
clone() { clone() {
console.error(`Call of abstract method clone(). Overwrite in subclass.`, this) console.error(`Call of abstract method clone(). Overwrite in subclass.`, this)
} }
@ -134,6 +146,12 @@ export class GeoGraphics {
this._layer = layer this._layer = layer
} }
/**
* Map of the containing layer. Null if on no layer.
*
* @readonly
* @memberof GeoGraphics
*/
get map() { get map() {
let map = null let map = null
if (this.mapLayer) { if (this.mapLayer) {
@ -142,6 +160,13 @@ export class GeoGraphics {
return map return map
} }
/**
* MapLayer of the containing layer. Null if on no layer.
*
* @member {MapLayer}
* @readonly
* @memberof GeoGraphics
*/
get mapLayer() { get mapLayer() {
let mapLayer = null let mapLayer = null
if (this.layer) { if (this.layer) {
@ -200,6 +225,10 @@ export class GeoGraphics {
* *
* This GeoGraphics does not provide any visual representation. * This GeoGraphics does not provide any visual representation.
* Draw the desired shape in the onDraw callback. * Draw the desired shape in the onDraw callback.
*
* @export
* @class GeoPoint
* @extends {GeoGraphics}
*/ */
export class GeoPoint extends GeoGraphics { export class GeoPoint extends GeoGraphics {
clone() { clone() {
@ -229,6 +258,13 @@ export class GeoPoint extends GeoGraphics {
_draw() {} _draw() {}
} }
/**
* Represensts a line between two locations.
*
* @export
* @class GeoLine
* @extends {GeoGraphics}
*/
export class GeoLine extends GeoGraphics { export class GeoLine extends GeoGraphics {
/** /**
* @param {object} opts - Optional values * @param {object} opts - Optional values
@ -331,6 +367,13 @@ export class GeoLine extends GeoGraphics {
} }
} }
/**
* Represents a shape on a map.
*
* @export
* @class GeoShape
* @extends {GeoGraphics}
*/
export class GeoShape extends GeoGraphics { export class GeoShape extends GeoGraphics {
clone() { clone() {
return new GeoShape(this.coordinates, this._cloneOptions) return new GeoShape(this.coordinates, this._cloneOptions)
@ -479,6 +522,9 @@ export class GeoShape extends GeoGraphics {
* The MultiGraphics makes use of the other GeoGraphics to * The MultiGraphics makes use of the other GeoGraphics to
* create a single graphics element out of multiple different * create a single graphics element out of multiple different
* GeoGraphics. * GeoGraphics.
*
* @class GeoMultiGraphics
* @extends {GeoGraphics}
*/ */
class GeoMultiGraphics extends GeoGraphics { class GeoMultiGraphics extends GeoGraphics {
/** /**
@ -528,9 +574,8 @@ class GeoMultiGraphics extends GeoGraphics {
} }
} }
/** /**
* * Text that is attatched to a GeoPoint.
* *
* @export * @export
* @class GeoText * @class GeoText
@ -574,6 +619,8 @@ export class GeoText extends GeoPoint {
/** /**
* A geotext with a notch at a certain Position. * A geotext with a notch at a certain Position.
*
* TODO: This is a specialization for the Tüsch. This should be inside the Tüsch project.
*/ */
export class GeoFlagLabel extends GeoText { export class GeoFlagLabel extends GeoText {
constructor(coordinates, text, opts) { constructor(coordinates, text, opts) {
@ -679,6 +726,13 @@ export class GeoFlagLabel extends GeoText {
} }
} }
/**
* The GeoMultiShape displays multiple forms.
*
* @export
* @class GeoMultiShape
* @extends {GeoShape}
*/
export class GeoMultiShape extends GeoShape { export class GeoMultiShape extends GeoShape {
static _manipulatePoints(points, func) { static _manipulatePoints(points, func) {
points.forEach(shape => { points.forEach(shape => {
@ -718,4 +772,3 @@ export class GeoMultiShape extends GeoShape {
}) })
} }
} }

View File

@ -39,6 +39,15 @@ export default class GeoJson {
return ['Point', 'LineString', 'Polygon', 'MultiPoint', 'MultiLineString', 'MultiPolygon'] return ['Point', 'LineString', 'Polygon', 'MultiPoint', 'MultiLineString', 'MultiPolygon']
} }
/**
* Takes a JSON object that contains a FeatureCollection and returns an array
* of GeoJson objects.
*
* @static
* @param {array} featureCollection - Array of GeoJSON objects that were contained in the feature collection.
* @returns {array} Returns an array of geo json objects.
* @memberof GeoJson
*/
static unwrapFeatureCollection(featureCollection) { static unwrapFeatureCollection(featureCollection) {
if (featureCollection.features == null) { if (featureCollection.features == null) {
console.error( console.error(
@ -63,12 +72,22 @@ export default class GeoJson {
return list return list
} }
/**
* Validates and converts one set of coordinates of a specific type.
*
* @static
* @param {string} type - Type of the GeoJson.
* @param {array} coordinates - array of points.
* @returns {array}
* @memberof GeoJson
*/
static validateAndConvert(type, coordinates) { static validateAndConvert(type, coordinates) {
let converted = null
if (!GeoJson.validateType(type)) throw new GeoJson.InvalidTypeError(type) if (!GeoJson.validateType(type)) throw new GeoJson.InvalidTypeError(type)
else { else {
if (GeoJson.validateCoordinates(type, coordinates)) { if (GeoJson.validateCoordinates(type, coordinates)) {
let converted = GeoJson.convert(type, coordinates) converted = GeoJson.convert(type, coordinates)
return converted
} else { } else {
console.error( console.error(
`Coordinates are invalid. They must be in format of type '${type} - ${GeoJson._getFormatStringOfType( `Coordinates are invalid. They must be in format of type '${type} - ${GeoJson._getFormatStringOfType(
@ -77,6 +96,8 @@ export default class GeoJson {
) )
} }
} }
return converted
} }
static validateType(type) { static validateType(type) {

View File

@ -9,6 +9,9 @@ import { ScatterContainer } from '../scatter.js'
* The GeoLayer is a special PIXILayer, that recognizes other GeoLayers and * 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 * GeoGraphics. The layer can be adapted to a map and notifies all Geo-Children
* of the Adaption. * of the Adaption.
*
* @export
* @class GeoLayer
*/ */
export class GeoLayer { export class GeoLayer {
constructor(displayObject, opts = {}) { constructor(displayObject, opts = {}) {
@ -264,7 +267,7 @@ export class MapLayer extends GeoLayer {
listeners: onChange listeners: onChange
}) })
this.mapview = new MapViewport({ this._mapview = new MapViewport({
zoom, zoom,
focus, focus,
viewport viewport
@ -284,6 +287,18 @@ export class MapLayer extends GeoLayer {
this.changeMap(mapList.active) this.changeMap(mapList.active)
} }
//Todo: rename to mapviewport.
/**
* Returns the MapViewport of this map layer.
*
* @readonly
* @member {MapViewport}
* @memberof MapLayer
*/
get mapview() {
return this._mapview
}
get mapChangeLocked() { get mapChangeLocked() {
return this._mapChangeLocked return this._mapChangeLocked
} }
@ -311,7 +326,7 @@ export class MapLayer extends GeoLayer {
} }
transformed(e) { transformed(e) {
this.mapview.transformed(this.map) this.mapview.update(this.map)
this.layers.forEach(layer => layer.parentMapLayerTransformed(this)) this.layers.forEach(layer => layer.parentMapLayerTransformed(this))
this.transformHandler.call(this) this.transformHandler.call(this)
} }
@ -321,7 +336,7 @@ export class MapLayer extends GeoLayer {
* *
* @param {ScatterContainer} scatterContainer - ScatterContainer of the app. * @param {ScatterContainer} scatterContainer - ScatterContainer of the app.
* @param {PIXI.DisplayObject} [container=null] - Container of the newly created MapLayer. If null, an empty PIXI.Container will be created. * @param {PIXI.DisplayObject} [container=null] - Container of the newly created MapLayer. If null, an empty PIXI.Container will be created.
* @returns * @returns {MapLayer} - Returns the cloned MapLayer.
* @memberof MapLayer * @memberof MapLayer
*/ */
clone(scatterContainer, container = null) { clone(scatterContainer, container = null) {

View File

@ -54,7 +54,6 @@ export class GeoMap {
this.onTransform = new EventHandler('transform', { listeners: onTransform }) this.onTransform = new EventHandler('transform', { listeners: onTransform })
this._alpha = alpha this._alpha = alpha
this.cover = cover
this.debug = debug this.debug = debug
//TODO discuss if this is required here. //TODO discuss if this is required here.
@ -68,7 +67,7 @@ export class GeoMap {
this.scalable = scalable this.scalable = scalable
this.viewport = viewport this.viewport = viewport
this.mapdata = mapdata this._mapdata = mapdata
this.overlays = {} this.overlays = {}
/** /**
@ -92,6 +91,32 @@ export class GeoMap {
} }
} }
/**
* Returns the image object used by the GeoMap.
*
* @readonly
* @memberof GeoMap
*/
get image() {
return this._image
}
/**
* The mapdata of the map.
*
* @member {MapData}
* @readonly
* @memberof GeoMap
*/
get mapdata() {
return this._mapdata
}
/**
* Clears all EventHandlers.
*
* @memberof GeoMap
*/
flushHandlers() { flushHandlers() {
this.onLoad.empty() this.onLoad.empty()
this.onTransform.empty() this.onTransform.empty()
@ -174,6 +199,7 @@ export class GeoMap {
/** /**
* Wrapps the display object around a scatter object. * Wrapps the display object around a scatter object.
* creates the image.
* *
* @private * @private
* @param {DisplayObject} displayObject - Defines the display object that will be wrapped inside the scatter object. * @param {DisplayObject} displayObject - Defines the display object that will be wrapped inside the scatter object.
@ -182,7 +208,7 @@ export class GeoMap {
load(image, renderer, frame = null, scatter = null) { load(image, renderer, frame = null, scatter = null) {
if (this.debug) console.log('Load image: ', image, frame) if (this.debug) console.log('Load image: ', image, frame)
this.image = image this._image = image
if (frame) this.setFrame(frame) if (frame) this.setFrame(frame)
let scatterOpts = Object.assign({ let scatterOpts = Object.assign({
@ -331,15 +357,41 @@ export class GeoMap {
this.frame = null 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) { setFrame(frame) {
if (this.debug) console.log('Set Frame: ', frame)
this.frame = frame this.frame = frame
} }
/** /**
* Gets the frame if the map.
*
* Frame is the display in which the map is shown. * Frame is the display in which the map is shown.
* Normally it's the app, but it can be another element, * Normally it's the app, but it can be another element,
* for example when in a submap. * for example when in a submap.
*
* @returns {Frame} - Returns the frame of the map.
* @memberof GeoMap
*/ */
getFrame() { getFrame() {
let frame = { let frame = {
@ -425,6 +477,7 @@ export class GeoMap {
* for creating the maps. * for creating the maps.
* *
* @static * @static
* @private
* @param {object} json - The object containing multiple map data sets. * @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. * @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. * @returns {boolean} - True if all sets were valid. False otherwise.
@ -460,6 +513,7 @@ export class GeoMap {
*Validates of a single data set contains the valid data for creating a map. *Validates of a single data set contains the valid data for creating a map.
* *
* @static * @static
* @private
* @param {object} json - The object containing a single set of map data. * @param {object} json - The object containing a single set of map data.
* @returns {boolean} - True if valid, otherwise false. * @returns {boolean} - True if valid, otherwise false.
* @memberof GeoMap * @memberof GeoMap
@ -516,11 +570,10 @@ GeoMap.counter = 0
* The DeepZoomMap class extends the GeoMap to create * The DeepZoomMap class extends the GeoMap to create
* maps as deepzoom images from maptiles. * maps as deepzoom images from maptiles.
* *
* @extends GeoMap * @export
* @class * @class DeepZoomMap
* @see {@link maps.html} * @extends {GeoMap}
*/ */
export class DeepZoomMap extends GeoMap { export class DeepZoomMap extends GeoMap {
/** /**
* @constructor * @constructor
@ -741,11 +794,13 @@ DeepZoomMap.tintcolors = [0xff0000, 0xff00ff, 0xffff00, 0x00ff00, 0x00ffff, 0x00
DeepZoomMap.tintcolor = 0 DeepZoomMap.tintcolor = 0
/** /**
*
* ImageMap extends GeoMap to display simple images * ImageMap extends GeoMap to display simple images
* as maps. * as maps.
*
* @export
* @class ImageMap
* @extends {GeoMap}
*/ */
export class ImageMap extends GeoMap { export class ImageMap extends GeoMap {
constructor(sprite, mapdata, opts = {}) { constructor(sprite, mapdata, opts = {}) {
super(mapdata, opts) super(mapdata, opts)

View File

@ -7,6 +7,22 @@ import { Points } from '../../utils.js'
import Logging from '../../logging.js' import Logging from '../../logging.js'
import { MapList } from './maplist.js' import { MapList } from './maplist.js'
/**
* A PIXI.Point or object in form {x,y}.
*
* @typedef {Object} Point
* @property {number} x - Indicates whether the Courage component is present.
* @property {number} y - Indicates whether the Power component is present.
*/
/**
* A coordinate point is a PIXI.Point or object in form {x,y} that contains map coordinates
* instead of pixel values, where x represents the latitude and y the longitude.
* @typedef {Object} CoordinatePoint
* @property {number} x - Indicates whether the Courage component is present.
* @property {number} y - Indicates whether the Power component is present.
*/
/** /**
* MapApp is responsible for showing fullscreen * MapApp is responsible for showing fullscreen
* map applications. * map applications.
@ -16,6 +32,10 @@ import { MapList } from './maplist.js'
* @extends {PIXIApp} * @extends {PIXIApp}
*/ */
export default class MapApp extends PIXIApp { export default class MapApp extends PIXIApp {
/**
*Creates an instance of MapApp.
* @memberof MapApp
*/
constructor(opts = {}) { constructor(opts = {}) {
super(opts) super(opts)
@ -30,7 +50,6 @@ export default class MapApp extends PIXIApp {
coordsLogging: false, coordsLogging: false,
overlays: {}, overlays: {},
keycodes: {}, keycodes: {},
showHotkeys: false,
imageMapZoomHeight: 256, //Defines the zoomvalue 1 for all image maps inside the mapapp. imageMapZoomHeight: 256, //Defines the zoomvalue 1 for all image maps inside the mapapp.
focus: null, focus: null,
zoom: 1, zoom: 1,
@ -47,7 +66,6 @@ export default class MapApp extends PIXIApp {
this.overlayElements = new Map() this.overlayElements = new Map()
this.debug = opts.debug this.debug = opts.debug
this.fpsLogging = opts.fpsLogging this.fpsLogging = opts.fpsLogging
this.showHotkeys = opts.showHotkeys
this.keycodes = this._extractKeyCodes(opts.keycodes) this.keycodes = this._extractKeyCodes(opts.keycodes)
this.coordsLogging = opts.coordsLogging this.coordsLogging = opts.coordsLogging
this.overlays = opts.overlays this.overlays = opts.overlays
@ -109,9 +127,15 @@ export default class MapApp extends PIXIApp {
console.log(JSON.stringify(boundaries)) console.log(JSON.stringify(boundaries))
} }
/**
* Creates the MapLayer.
*
* @private
* @memberof MapApp
*/
_setupMapLayer() { _setupMapLayer() {
this.mapContainer = new PIXI.Container() this.mapContainer = new PIXI.Container()
this.mapLayer = new MapLayer(this.mapList, this.scene, this.mapContainer, { this._mapLayer = new MapLayer(this.mapList, this.scene, this.mapContainer, {
name: 'Root Map Layer', name: 'Root Map Layer',
focus: this.focus, focus: this.focus,
zoom: this.zoom, zoom: this.zoom,
@ -177,12 +201,27 @@ export default class MapApp extends PIXIApp {
} }
} }
/**
* Relayouts the app. E.g. called when the window is resized.
*
* @param {number} width - Desired width of the app.
* @param {number} height - Desired height of the app.
* @memberof MapApp
*/
layout(width, height) { layout(width, height) {
this.scene.resize(width, height) this.scene.resize(width, height)
this.mapLayer.mapview.update() this.mapLayer.mapview.update()
this.onSizeChanged.call(this) this.onSizeChanged.call(this)
} }
/**
* Overrides the sceneFactory of the PIXIApp to create a RigidScatterContainer instead of
* a regular PIXI.Container()
*
* @private
* @returns {RigidScatterContainer} - Returns the newly created RigidScatterContainer.
* @memberof MapApp
*/
sceneFactory() { sceneFactory() {
return new RigidScatterContainer(this.width, this.height, this.renderer, { return new RigidScatterContainer(this.width, this.height, this.renderer, {
app: this, app: this,
@ -193,6 +232,12 @@ export default class MapApp extends PIXIApp {
}) })
} }
/**
* Changes the map to the given key.
*
* @param {string} key - Identifier of the map to change to.
* @memberof MapApp
*/
selectMap(key) { selectMap(key) {
if (this.debug) console.log('Select map', key, result) if (this.debug) console.log('Select map', key, result)
let result = this.mapList.select(key) let result = this.mapList.select(key)
@ -214,14 +259,30 @@ export default class MapApp extends PIXIApp {
this.selectMap(key) this.selectMap(key)
} }
/**
* Adds a map to the maplist.
* If no map is set, the added map will be set as default.
*
* @param {string} key - Identifier for the map.
* @param {GeoMap} map - Map object to add.
* @memberof MapApp
*/
addMap(key, map) { addMap(key, map) {
if (this.mapList) this.mapList.add(key, map) if (this.mapList) this.mapList.add(key, map)
else console.error('Cannot access mapLayer. It was not initialized yet.') else console.error('Cannot access mapLayer. It was not initialized yet.')
} }
/**
* Adds multiple maps at once.
*
* @param {object} mapObject
* @memberof MapApp
*/
addMaps(mapObject) { addMaps(mapObject) {
for (let [key, val] of Object.entries(mapObject)) { for (let [key, val] of Object.entries(mapObject)) {
if (val instanceof GeoMap) {
this.addMap(key, val) this.addMap(key, val)
} else console.warn('Tried adding maps that are not og Type GeoMap.')
} }
} }
@ -254,6 +315,16 @@ export default class MapApp extends PIXIApp {
} }
} }
/**
* Returns the mapLayer of the map.
*
* @member {MapLayer}
* @memberof MapApp
*/
get mapLayer() {
return this._mapLayer
}
_doesOverlayElementExist(layer, type, name) { _doesOverlayElementExist(layer, type, name) {
let layerElements = this.overlayElements.get(layer) let layerElements = this.overlayElements.get(layer)
return layerElements != undefined && layerElements[type] != null && layerElements[type][name] != null return layerElements != undefined && layerElements[type] != null && layerElements[type][name] != null
@ -276,7 +347,9 @@ export default class MapApp extends PIXIApp {
} }
/** /**
* Copies the current coordinates to the clipboard. * Copies the current location to the clipboard.
*
* @memberof MapApp
*/ */
locationToClipboard() { locationToClipboard() {
let hidden = document.createElement('input') let hidden = document.createElement('input')
@ -289,6 +362,15 @@ export default class MapApp extends PIXIApp {
document.body.removeChild(hidden) document.body.removeChild(hidden)
} }
/**
* Can be used to copy polygons to the clipboard.
*
* Useful for debugging or to roughly trace a shape in the map.
* The generated pointarray can be used as geometry of a geographic
* or inside an overlay to draw that shape onto the map.
*
* @memberof MapApp
*/
pathToClipboard() { pathToClipboard() {
let hidden = document.createElement('input') let hidden = document.createElement('input')
document.body.appendChild(hidden) document.body.appendChild(hidden)
@ -312,25 +394,27 @@ export default class MapApp extends PIXIApp {
document.body.removeChild(hidden) document.body.removeChild(hidden)
} }
/**
* Returns the active map.
*
* @readonly
* @memberof MapApp
*/
get map() { get map() {
return this.mapList.map return this.mapList.map
} }
get activeMapKey() {
return this.mapLayer.active
}
getRelativePosition(x, y) {
return {
x: x * app.width,
y: y * app.height
}
}
clearDrawData() { clearDrawData() {
this.drawData = [] this.drawData = []
} }
/**
* Logs a text field on the map.
* The text element is a DOMElement.
*
* @param {string} msg - Message to log.
* @memberof MapApp
*/
showNotification(msg) { showNotification(msg) {
let notification = document.createElement('div') let notification = document.createElement('div')
notification.classList.add('notification') notification.classList.add('notification')
@ -373,8 +457,25 @@ export default class MapApp extends PIXIApp {
}) })
} }
_currentLocationToString() {} /**
* @typedef KeyCode
* @type {object}
* @property {string} key - an ID.
* @property {boolean} altKey - Defines if KeyCode requires the alt key to be pressed.
* @property {boolean} shiftKey - Defines if KeyCode requires the shift key to be pressed.
* @property {boolean} ctrlKey - Defines if KeyCode requires the ctrl key to be pressed.
*
*/
/**
* Check's if a key event matches a defined KeyCode.
*
* @private
* @param {KeyboardEvent} event - Event that is fired on keydown, -pressed or -up.
* @param {KeyCode} keyCode - KeyCode is an object in the form of : {key:number, altKey:boolean,shiftKey:boolean,ctrlKey:boolean }
* @returns {boolean} - True, when the event matches the keycode, false otherwise.
* @memberof MapApp
*/
_matchKeyCode(event, keyCode) { _matchKeyCode(event, keyCode) {
// If keycode does not exist or is invalid - return. // If keycode does not exist or is invalid - return.
if (!keyCode || keyCode.key == null) return false if (!keyCode || keyCode.key == null) return false
@ -396,6 +497,13 @@ export default class MapApp extends PIXIApp {
}) })
} }
/**
* Checks on every key down if it matches a keycode.
*
* @private
* @param {KeyboardEvent} event
* @memberof MapApp
*/
_checkForKeyCode(event) { _checkForKeyCode(event) {
if (this._matchKeyCode(event, this.keycodes.copyCoordinate)) { if (this._matchKeyCode(event, this.keycodes.copyCoordinate)) {
event.preventDefault() event.preventDefault()
@ -454,10 +562,28 @@ export default class MapApp extends PIXIApp {
this.drawMode = this.DRAW_MODES.PIXI_POINT this.drawMode = this.DRAW_MODES.PIXI_POINT
} }
_extractKeyCodes(keycodeText) { /**
* @typedef KeyCodePairs
* @type {object}
* @property {string} name - Name of the KeyCode.
* @property {KeyCode} keyCode - KeyCode
*
*/
/**
* Extracts keycodes from a string.
*
* KeycodeStrings may look like this 'ctrl+shift+b'
*
* @private
* @param {KeyCodePairs} nameKeyCodePairs
* @returns {array} - Returns an array of KeyCode objects.
* @memberof MapApp
*/
_extractKeyCodes(nameKeyCodePairs) {
let out = {} let out = {}
for (let [name, combinationString] of Object.entries(keycodeText)) { for (let [name, combinationString] of Object.entries(nameKeyCodePairs)) {
let keys = combinationString.split('+') let keys = combinationString.split('+')
out[name] = { out[name] = {
key: null, key: null,
@ -528,35 +654,8 @@ export default class MapApp extends PIXIApp {
const center = submap.center const center = submap.center
const radius = submap.container.width / 2 const radius = submap.container.width / 2
const distance = Points.distance(center, event.data.global) / submap.scatter.scale const distance = Points.distance(center, event.data.global) / submap.scatter.scale
if (distance > radius) {
//submap.resize((distance) * 2, .2)
//submap.centerAt(myevent.center)
} }
} }
// for (const submap of this.submaps) {
// const center = submap.center
// const radius = submap.container.width / 2
// const distance = Points.distance(center, event.data.global) / submap.scatter.scale
// const inside = distance < radius + 10
// console.log(distance, radius)
// if (inside) {
// // (this.width + 80) / 2 * this.scatter.scale
// //const width = (submap.width + 80) / 2 * submap.scatter.scale
// //console.log(width)
// if (distance > radius) {
// submap.resize((distance) * 2, .2)
// }
// } else {
// if (distance < radius + 20) {
// //submap.resize((distance - 30) * 2, .2)
// }
// }
// }
}
} }
__onEnd(event) { __onEnd(event) {

View File

@ -25,7 +25,7 @@ export class MapData {
opts opts
) )
this.projection = projection this._projection = projection
if (this.opts.clip) { if (this.opts.clip) {
let _cmin = this.projection.forward(this.opts.clip.min) let _cmin = this.projection.forward(this.opts.clip.min)
@ -49,12 +49,23 @@ export class MapData {
} }
} }
/**
* The projection used by the mapdata.
*
* @member {Projection}
* @readonly
* @memberof MapData
*/
get projection() {
return this._projection
}
/** /**
* Transforms a pixel point on the map to a geographical coordinate. * Transforms a pixel point on the map to a geographical coordinate.
* *
* @public * @public
* @param {{x,y} | PIXI.Point} point - A pixel position on the map. * @param {Point} point - A pixel position on the map.
* @returns {{x,y} | PIXI.Point} - A geographical coordinate. * @returns {CoordinatePoint} A geographical coordinate.
* @memberof MapData * @memberof MapData
*/ */
toCoordinates(point) { toCoordinates(point) {
@ -86,8 +97,8 @@ export class MapData {
* Transform a geographical coordinate to a pixel point on the map. * Transform a geographical coordinate to a pixel point on the map.
* *
* @public * @public
* @param {{x,y} | PIXI.Point} coordinates - A point in the form of {x:lat,y:lng}. * @param {CoordinatePoint} coordinates - A point in the form of {x:lat,y:lng}.
* @returns {{x,y} | PIXI.Point} point - A pixel position on the map. * @returns {Point} A pixel position on the map.
* @memberof MapData * @memberof MapData
*/ */
toPixel(coordinates) { toPixel(coordinates) {
@ -118,17 +129,18 @@ export class MapData {
return point return point
} }
/** /**
* Get's the clipping of the map data. Clipping describes the * Clipping describes the
* piece of the map that is shown. E.g. if we just show a map of * piece of the map that is shown. The returned object contains a min and max value of the clipping in form of: {min: {x,y}, max:{x,y}}. Where x and y are in between 0 and 1.
*
* E.g. if we just show a map of
* europe, then we have to set the clipping properly, otherwise * europe, then we have to set the clipping properly, otherwise
* the preojection would produce the wrong results when transforming * the preojection would produce the wrong results when transforming
* from a point to coordinates or the other way around. * from a point to coordinates or the other way around.
* *
* @readonly * @readonly
* @member {object}
* @memberof MapData * @memberof MapData
* @returns {object} - Object that contains a min and max value of the clipping in form of: {min: {x,y}, max:{x,y}}. Where x and y are in between 0 and 1.
*/ */
get clip() { get clip() {
let unclipped = { let unclipped = {
@ -139,7 +151,6 @@ export class MapData {
return this.opts.clip ? this.opts.clip : unclipped return this.opts.clip ? this.opts.clip : unclipped
} }
/** /**
* Returns the biggest viewport the mapdata allows. * Returns the biggest viewport the mapdata allows.
* This is determined by the projecton or the clipping on the mapapp. * This is determined by the projecton or the clipping on the mapapp.
@ -152,7 +163,6 @@ export class MapData {
} }
} }
/** /**
* Special mapdata for DeepZoomMap objects. * Special mapdata for DeepZoomMap objects.
* *

View File

@ -7,8 +7,8 @@
*/ */
export class MapList { export class MapList {
constructor(active = null, maps = {}) { constructor(active = null, maps = {}) {
this.maps = maps this._maps = maps
this.active = active this._active = active
if (Object.keys(maps).length > 0) this.select(active) if (Object.keys(maps).length > 0) this.select(active)
} }
@ -37,7 +37,7 @@ export class MapList {
} }
if (this.active !== active) { if (this.active !== active) {
this.active = active this._active = active
map = this.maps[active] map = this.maps[active]
} }
} else { } else {
@ -75,7 +75,7 @@ export class MapList {
*/ */
add(key, map) { add(key, map) {
if (this.maps[key] != null) consol.warn('Key already in mapList. The existing key was overwritten.') if (this.maps[key] != null) consol.warn('Key already in mapList. The existing key was overwritten.')
if (this.active == null) this.active = key if (this.active == null) this._active = key
map.name = key map.name = key
this.maps[key] = map this.maps[key] = map
} }
@ -86,12 +86,35 @@ export class MapList {
* *
*@public *@public
* @readonly * @readonly
* @member {GeoMap}
* @memberof MapList * @memberof MapList
*/ */
get map() { get map() {
return this.maps && this.maps[this.active] ? this.maps[this.active] : null return this.maps && this.maps[this.active] ? this.maps[this.active] : null
} }
/**
* Returns the list of addedd cards.
*
* @member {object}
* @readonly
* @memberof MapList
*/
get maps() {
return this._maps
}
/**
* Returns the active key.
*
* @member {string}
* @readonly
* @memberof MapList
*/
get active() {
return this._active
}
/** /**
* Selects the next map in the map array. * Selects the next map in the map array.
* *

View File

@ -1,7 +1,7 @@
import { DeepZoomMap } from './map.js' import { DeepZoomMap } from './map.js'
/** /**
* The MapView class is responsible for a consistent map view. * The MapViewport class is responsible for a consistent map view.
* It is aware of the current viewposition, the scale and viewport. * It is aware of the current viewposition, the scale and viewport.
* It ensures, that maps can be changed, without the user noticing it. * It ensures, that maps can be changed, without the user noticing it.
* *
@ -11,7 +11,7 @@ export default class MapViewport {
* *
* @param {object} [focus = {x:0, y:0}] - Defines the startup focuspoint of the app. * @param {object} [focus = {x:0, y:0}] - Defines the startup focuspoint of the app.
* @param {number} [zoom = 0] - Defines the startup zoom of the app. Note that this is just a request. * @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 * The MapViewport will prioritize a full scale app, than displaying the demanded zoom factor
*/ */
constructor({ 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.viewport = viewport
@ -20,32 +20,71 @@ export default class MapViewport {
this.referenceHeight = 256 this.referenceHeight = 256
} }
/**
* The current focus point as map coordinates.
*
* @member {CoordinatePoint}
* @readonly
* @memberof MapViewport
*/
get focus() { get focus() {
return this._focus return this._focus
} }
/**
* The current zoom distance.
* On DeepZoomMaps this is equal to the zoom level.
*
* @member {number}
* @readonly
* @memberof MapViewport
*/
get zoom() { get zoom() {
return this._zoom return this._zoom
} }
/**
* Applies the current view to a map.
*
*
* @param {GeoMap} map - Map the viewport should be applied to.
* @memberof MapViewport
*/
apply(map) { apply(map) {
map.moveTo(this._focus, this._zoom) map.moveTo(this._focus, this._zoom)
} }
transformed(map) { /**
* Update the focus point and zoom according to the map.
* This is commonly called when the map is transformed.
*
* @param {GeoMap} map - Map to update.
* @memberof MapViewport
*/
update(map) {
this.updateZoom(map) this.updateZoom(map)
this.updateFocusPoint(map) this.updateFocusPoint(map)
} }
updatePosition(map) { /**
this.updateFocusPoint(map) * Updates the focus point.
this.updateZoom(map) * This is automatically called when calling update.
} *
* @param {GeoMap} map
* @memberof MapViewport
*/
updateFocusPoint(map) { updateFocusPoint(map) {
const frame = map.getFrame() const frame = map.getFrame()
this._focus = this.coordinatesFromWindowPoint(map, frame.localCenter) this._focus = this.coordinatesFromWindowPoint(map, frame.localCenter)
} }
/**
* Updates the zoom.
* This is automatically called when calling update.
*
* @param {GeoMap} map
* @memberof MapViewport
*/
updateZoom(map) { updateZoom(map) {
/** /**
* TODO: This relies on the fact, that all maps have the same tileSize, * TODO: This relies on the fact, that all maps have the same tileSize,
@ -57,6 +96,16 @@ export default class MapViewport {
} }
} }
/**
* Transforms a position on the map to a position in the window.
*
* Inverse of windowPointToMapPoint.
*
* @param {GeoMap} map - Map to calculate the position on.
* @param {Point} point - Position on the map in pixels.
* @returns {Point} - Pixel position in the window.
* @memberof MapViewport
*/
mapPointToWindowPoint(map, point) { mapPointToWindowPoint(map, point) {
let windowPoint = { x: 0, y: 0 } let windowPoint = { x: 0, y: 0 }
@ -76,6 +125,16 @@ export default class MapViewport {
return windowPoint return windowPoint
} }
/**
* Transforms in the window to a position on the map.
*
* Inverse of mapPointToWindowPoint(map, point) {.
*
* @param {GeoMap} map - Map to calculate the position on.
* @param {Point} point -Pixel position in the window.
* @returns {Point} - Position on the map in pixels.
* @memberof MapViewport
*/
windowPointToMapPoint(map, point) { windowPointToMapPoint(map, point) {
let pointOnMap = { x: 0, y: 0 } let pointOnMap = { x: 0, y: 0 }
if (map['image'] && map.image['parent']) { if (map['image'] && map.image['parent']) {

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" class="dark-mode">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@ -33,6 +33,7 @@
<body onload="Doctest.run()"> <body onload="Doctest.run()">
<h1 class="title">Overlay</h1> <h1 class="title">Overlay</h1>
<!-- <a href="../../../" class="Documentation"></a> -->
<p class="description"> <p class="description">
The overlayclass creates a convenient way to create and design The overlayclass creates a convenient way to create and design
complex map overlays. complex map overlays.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 55 KiB