Widget:Karte v2/SMWTest
Zur Navigation springen
Zur Suche springen
*/
/**
* widget-map-floors
* https://github.com/GW2Wiki/widget-map-floors
*
* Created by Smiley on 11.06.2016.
* https://github.com/codemasher
* https://wiki.guildwars2.com/wiki/User:Smiley-1
*
* scripts & libraries used:
*
* https://leafletjs.com/
* http://vanilla-js.com/
*/
'use strict';
const GW2MapOptions = {
// errorTile : 'https://wiki.guildwars2.com/images/a/af/Widget_Map_floors_blank_tile.png',
initLayers : [
'region_label','map_label','task_icon','heropoint_icon','waypoint_icon','landmark_icon','vista_icon',
'unlock_icon','masterypoint_icon','adventure_icon','jumpingpuzzle_icon', 'sector_label',
],
};
/**
* Class GW2Map
*/
class GW2Map {
// common settings for all maps
options = {
containerClassName: 'gw2map',
linkboxClassName : 'gw2map-linkbox', // additional to containerClassName
navClassName : 'gw2map-nav',
lang : 'en',
initLayers : null,
mapAttribution : true,
errorTile : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAADHUlEQVR4nO3UMQEAIAzAsIF/zyBjRxMFvXpm5g2QdLcDgD0GAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEGAGEfdCIC/5NkVo8AAAAASUVORK5CYII=',
padding : 0.5,
defaultZoom : 4,
minZoom : 0,
maxZoom : 7,
fullscreenControl : true,
coordView : true,
apiBase : 'https://api.guildwars2.com',
tileBase : 'https://tiles.guildwars2.com/',
tileExt : '.jpg',
colors : {
map_poly : 'rgba(255, 255, 255, 0.5)',
region_poly: 'rgba(255, 155, 255, 0.5)',
sector_poly: 'rgba(40, 140, 25, 0.5)',
task_poly : 'rgba(250, 250, 30, 0.5)',
event_poly : 'rgba(210, 125, 40, 0.5)',
},
};
iconZoomLayers = [
'waypoint_icon',
'landmark_icon',
'vista_icon',
'heropoint_icon',
'task_icon',
'unlock_icon',
'masterypoint_icon',
'adventure_icon',
'jumpingpuzzle_icon',
'region_label',
'map_label',
'sector_label',
'event_icon',
'lavatubes',
];
linkboxExclude = [
'region_label',
'region_poly',
'map_poly',
'sector_poly',
'task_poly',
'event_poly',
];
// per-map options parsed from the container's dataset
dataset = {};
layers = {};
/**
* GW2Map constructor.
*
* @param {HTMLElement} container
* @param {string} id
* @param {Object} options
* @returns {GW2Map}
*/
constructor(container, id, options){
this.container = container;
this.id = id;
this.options = GW2MapUtil.extend(this.options, options);
this.dataset = new GW2MapDataset(this.container.dataset, this.options).getData();
}
/**
* @returns {GW2Map}
* @public
*/
init(){
if(this.dataset.linkbox){
this.linkbox = document.createElement('div');
this.linkbox.className = this.options.navClassName;
this.linkbox.style = 'max-height:'+this.container.clientHeight+'px;';
this.container.className += ' '+this.options.linkboxClassName;
this.container.parentNode.insertBefore(this.linkbox, this.container.nextSibling);
}
this._setBaseMap();
// build the request path @todo
let url = this.options.apiBase + '/v2/continents/' + this.dataset.continentId + '/floors/' + this.dataset.floorId;
url += this.dataset.regionId ? '/regions/' + this.dataset.regionId : '';
url += this.dataset.regionId && this.dataset.mapId ? '/maps/' + this.dataset.mapId : '';
url += '?wiki=1&lang=' + this.dataset.language;
this._request(url, '_renderFloor');
return this;
}
/**
* @param {string} url
* @param {string} callback
* @private
*/
_request(url, callback){
// xhr > fetch. DON'T @ ME
let request = new XMLHttpRequest();
request.open('GET', url, true);
request.addEventListener('load', ev => {
if(request.readyState === 4 && request.status === 200){
let json = JSON.parse(request.responseText);
if(typeof callback === 'string'){
return this[callback](json);
}
return callback(json);
}
console.log('(╯°□°)╯彡┻━┻ ', request.status);
});
request.send();
}
/**
* sets the base tiles and adds an optional copyright info
*
* @returns {GW2Map}
* @private
*/
_setBaseMap(){
// the map object
this.map = L.map(this.container, {
crs : L.CRS.Simple,
minZoom : this.options.minZoom,
maxZoom : this.options.maxZoom,
attributionControl: this.options.mapAttribution,
zoomControl : this.dataset.mapControls,
fullscreenControl : this.options.fullscreenControl,
coordView : this.options.coordView,
});
// the main tile layer
L.tileLayer(null, {
// use the custom tile getter
tileGetter : (coords, zoom) => this._tileGetter(coords, zoom),
continuousWorld : true,
minZoom : this.options.minZoom,
maxZoom : this.options.maxZoom,
attribution : this.options.mapAttribution === true
? GW2MAP_I18N.attribution + ' © ArenaNet'
: false,
}).addTo(this.map);
// add the layer controls
if(this.dataset.mapControls){
this.controls = L.control.layers().addTo(this.map);
}
return this;
}
/**
* @todo https://github.com/arenanet/api-cdi/pull/61
* @todo https://github.com/arenanet/api-cdi/pull/62
* @todo https://github.com/arenanet/api-cdi/issues/308
*
* @param {*} json
* @private
*/
_renderFloor(json){
// transform the response to GeoJSON - polyfill for https://github.com/arenanet/api-cdi/pull/62
this.floorGeoJSON = new GW2FloorGeoJSON(json, this.dataset.customRect, this.dataset.extraLayers, this.dataset.includeMaps);
let geojson = this.floorGeoJSON.getData();
this.viewRect = geojson.viewRect; // set viewRect for the tile getter
let rect = new GW2ContinentRect(this.viewRect).getBounds();
let bounds = new L.LatLngBounds(this._p2ll(rect[0]), this._p2ll(rect[1])).pad(this.options.padding);
let center = bounds.getCenter();
let coords = this.dataset.centerCoords || [];
if(coords.length === 2){
if(coords[0] > 0 && coords[0] <= 49152 && coords[1] > 0 && coords[1] <= 49152){
center = this._p2ll(coords);
}
}
this.map.setMaxBounds(bounds).setView(center, this.dataset.zoom);
let panes = Object.keys(geojson.featureCollections);
panes.forEach(pane => this._createPane(geojson.featureCollections[pane].getJSON(), pane, (this.dataset.initLayers || this.options.initLayers || panes)));
this.map.on('zoomend', ev => this._zoomEndEvent());
this._zoomEndEvent(); // invoke once to set the icon zoom on the newly created map
if(this.dataset.events){
this._renderEvents();
}
}
/**
* @private
*/
_zoomEndEvent(){
let zoom = this.map.getZoom();
this.iconZoomLayers.forEach(layer => {
if(!this.layers[layer]){
return;
}
let element = this.layers[layer].options.pane;
if(zoom >= 5){
PrototypeElement.removeClassName(element, 'half');
}
else if(zoom < 5 && zoom >= 3){
PrototypeElement.removeClassName(element, 'quarter');
PrototypeElement.addClassName(element, 'half');
}
else if(zoom < 3 && zoom >= 1){
PrototypeElement.removeClassName(element, 'half');
PrototypeElement.removeClassName(element, 'invis');
PrototypeElement.addClassName(element, 'quarter');
}
else if(zoom < 1){
PrototypeElement.removeClassName(element, 'quarter');
PrototypeElement.addClassName(element, 'invis');
}
});
}
/**
* @private
*/
_renderEvents(){
this._request(this.options.apiBase + '/v1/event_details.json?lang=' + this.dataset.language, event_details => {
this._request(this.options.apiBase + '/v1/maps.json?lang=' + this.dataset.language, maps => {
let eventGeoJSON = new GW2EventGeoJSON(event_details.events, maps.maps, this.floorGeoJSON.maps).getData();
let panes = Object.keys(eventGeoJSON.featureCollections);
panes.forEach(pane => {this._createPane(eventGeoJSON.featureCollections[pane].getJSON(), pane, (this.dataset.initLayers || this.options.initLayers || panes))});
});
});
}
/**
* @param {GW2FloorGeoJSON[]} geojson
* @param {string} pane
* @param {string[]}initLayers
* @private
*/
_createPane(geojson, pane, initLayers){
let name = ' ' + GW2MAP_I18N.layers[pane];
if(!this.layers[pane]){
this.layers[pane] = L.geoJson(geojson, {
pane : this.map.createPane(pane),
coordsToLatLng: coords => this._p2ll(coords),
pointToLayer : (feature, coords) => this._pointToLayer(feature, coords, pane),
onEachFeature : (feature, layer) => this._onEachFeature(feature, layer, pane),
style : (feature) => this._layerStyle(feature, pane),
});
this.controls.addOverlay(this.layers[pane], name)
}
else{
this.layers[pane].addData(geojson);
}
if(GW2MapUtil.in_array(pane, initLayers)){
this.layers[pane].addTo(this.map);
}
}
/**
* @link http://leafletjs.com/reference-1.5.0.html#geojson-oneachfeature
* @param {*} feature
* @param {L.Layer} layer
* @param {string} pane
* @private
*/
_onEachFeature(feature, layer, pane){
let p = feature.properties;
let content = '';
// no popup for event circles
// if(p.layertype === 'poly' && p.type === 'event'){
// return;
// }
if(p.layertype === 'icon'){
content +=
p.icon
? ''
: '';
}
if(p.name){
if(!GW2MapUtil.in_array(p.type, ['vista'])){
//noinspection RegExpRedundantEscape
let wikiname = p.name.toString()
.replace(/\.$/, '')
.replace(/\s/g, '_')
.replace(/(Mount\:_|Raid—)/, '');
content += '' + p.name + '';
}
else{
content += p.name;
}
}
if(p.level){
content += ' (' + p.level + ')';
}
else if(p.min_level && p.max_level){
content += ' (' + (p.min_level === p.max_level ? p.max_level : p.min_level + '-' + p.max_level) + ')';
}
if(p.chat_link){
if(content){
content += '
'; } content += ''; } if(p.description){ if(content){ content += '
'; } content += '';
input.readOnly = true;
container.appendChild(input);
L.DomEvent.disableClickPropagation(container);
L.DomEvent.on(input, 'click', ev => ev.target.select());
map.on('click', ev => {
let point = map.project(ev.latlng, map.options.maxZoom);
input.value = '['+Math.round(point.x)+', '+Math.round(point.y)+']';
// ckeckbox: copy to clipboard
// navigator.clipboard.writeText(input.value);
});
return container;
},
});
L.Map.mergeOptions({coordView: true});
L.Map.addInitHook(function () {
if (this.options.coordView) {
new L.Control.Coordview().addTo(this);
}
});
L.control.coordview = function(options){
return new L.Control.Coordview(options);
};
// override L.TileLayer.getTileUrl() and add a custom tile getter
L.TileLayer.include({
getTileUrl: function(coords){
let tileGetter = this.options.tileGetter;
if(typeof tileGetter === 'function'){
return tileGetter(coords, this._getZoomForUrl());
}
return false;
}
});
// auto center popups and align div/html icons
L.Popup.include({
_getAnchor: function(){
let anchor = this._source && this._source._getPopupAnchor
? this._source._getPopupAnchor()
: [0, 0];
if(typeof anchor === 'string' && anchor.toLowerCase() === 'auto'){
let style = {left: 0, top: 0, width: 0};
// is the layer active?
if(this._source._icon){
style = window.getComputedStyle(this._source._icon);
}
anchor = [
GW2MapUtil.intval(style.left) + Math.round(GW2MapUtil.intval(style.width) / 2),
GW2MapUtil.intval(style.top)
];
}
return L.point(anchor);
}
});
L.Marker.include({
_initIcon: function(){
let options = this.options;
let classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
let icon = options.icon.createIcon(this._icon);
let addIcon = false;
// if we're not reusing the icon, remove the old one and init new one
if(icon !== this._icon){
if(this._icon){
this._removeIcon();
}
addIcon = true;
if(options.title){
icon.title = options.title;
}
if(icon.tagName === 'IMG'){
icon.alt = options.alt || '';
}
}
L.DomUtil.addClass(icon, classToAdd);
if(options.keyboard){
icon.tabIndex = '0';
}
this._icon = icon;
if(options.riseOnHover){
this.on({
mouseover: this._bringToFront,
mouseout : this._resetZIndex,
});
}
if(options.opacity < 1){
this._updateOpacity();
}
if(addIcon){
this.getPane().appendChild(this._icon);
// set icon styles after the node is appended to properly get the computed dimensions
options.icon._setIconStyles(this._icon, 'icon', addIcon);
}
this._initInteraction();
}
});
L.Icon.include({
_setIconStyles:function(img, name, addIcon){
if(addIcon !== true){
return;
}
img.className = 'leaflet-marker-icon ' + (this.options.className || '');
let sizeOption = this.options.iconSize;
let anchor = this.options.iconAnchor;
if(typeof sizeOption === 'number'){
sizeOption = [sizeOption, sizeOption];
}
let size = L.point(sizeOption);
if(anchor && anchor.toString().toLowerCase() === 'auto'){
let origin = window.getComputedStyle(img).perspectiveOrigin.split(' ');
img.style.left = '-'+origin[0];
img.style.top = '-'+origin[1];
}
else{
anchor = L.point(anchor || size && size.divideBy(2, true));
if(anchor){
img.style.marginLeft = (-anchor.x) + 'px';
img.style.marginTop = (-anchor.y) + 'px';
}
}
if(size){
img.style.width = size.x + 'px';
img.style.height = size.y + 'px';
}
}
});
// save the GW2Map objects for later usage
// noinspection JSMismatchedCollectionQueryUpdate
let maps = [];
let mapOptions = GW2MapUtil.extend(GW2MapOptions, $options);
Object.keys($containers).forEach(id => {
maps[id] = new GW2Map($containers[id], id, mapOptions).init();
});
// console.log(maps);
});
})(GW2MapInvokerOptions, GW2MapContainers);
/* */
Daten aus der Infobox (leider nicht vorhanden).
/*
'; } content += ''; } if(p.description){ if(content){ content += '
'; } content += '
' + this._parseWikilinks(p.description) + '
';
}
if(content){
layer.bindPopup(content);
}
if(this.dataset.linkbox){
this._linkboxItem(feature, layer, pane)
}
}
/**
*
* @param {string} str
* @returns {string}
* @private
*/
_parseWikilinks(str){
return str
.replace(/\[\[([^\]\|]+)\]\]/gi, '$1')
.replace(/\[\[([^\|]+)(\|)([^\]]+)\]\]/gi, '$3');
}
/**
* @param {*} feature
* @param {L.Layer} layer
* @param {string} pane
* @private
*/
_linkboxItem(feature, layer, pane){
let p = feature.properties;
if(GW2MapUtil.in_array(pane, this.linkboxExclude) || p.mapID === -1){
return;
}
let navid = 'gw2map-navbox-map-'+p.mapID;
let nav = document.getElementById(navid);
if(!nav){
nav = document.createElement('div');
nav.id = navid;
nav.className = 'gw2map-navbox';
this.linkbox.appendChild(nav);
}
let paneContentID = 'gw2map-navbox-'+p.mapID+'-'+pane;
let paneContent = document.getElementById(paneContentID);
if(!paneContent && pane !== 'map_label'){
paneContent = document.createElement('div');
paneContent.id = paneContentID;
nav.appendChild(paneContent);
}
let item = document.createElement('span');
if(pane !== 'map_label'){
item.innerHTML = '';
}
item.innerHTML += (p.name || p.id || '-');
if(typeof layer.getLatLng === 'function'){
item.addEventListener('click', ev => {
let latlng = layer.getLatLng();
this.map
.panTo(latlng)
.openPopup(layer.getPopup(), latlng);
});
// insert the map label as first item
pane === 'map_label'
? nav.insertBefore(item, nav.firstChild)
: paneContent.appendChild(item);
}
}
/**
* @link http://leafletjs.com/reference-1.5.0.html#geojson-pointtolayer
* @param {*} feature
* @param {LatLng} coords
* @param {string} pane
* @private
*/
_pointToLayer(feature, coords, pane){
let icon;
let p = feature.properties;
if(p.layertype === 'poly' && p.type === 'event'){
return new L.Circle(coords, feature.properties.radius);
}
let iconParams = {
pane: pane,
iconSize : null,
popupAnchor: 'auto',
// temporarily adding the "completed" classname
// https://discordapp.com/channels/384735285197537290/384735523521953792/623750587921465364
className: 'gw2map-' + p.layertype + ' gw2map-' + p.type + '-' + p.layertype + ' completed'
};
if(p.icon){
iconParams.iconUrl = p.icon;
if(p.className){
iconParams.className += ' '+p.className;
}
icon = L.icon(iconParams);
}
else{
if(p.layertype === 'label'){
iconParams.html = p.name;
iconParams.iconAnchor = 'auto';
}
if(p.type === 'masterypoint'){
iconParams.className += ' ' + p.region.toLowerCase()
}
else if(p.type === 'heropoint'){
iconParams.className += p.id.split('-')[0] === '0' ? ' core' : ' expac';
}
icon = L.divIcon(iconParams);
}
return L.marker(coords, {
pane: pane,
title: p.layertype === 'icon' ? p.name : null,
icon: icon
});
}
/**
* @link http://leafletjs.com/reference-1.5.0.html#geojson-style
* @param {*} feature
* @param {string} pane
* @private
*/
_layerStyle(feature, pane){
let p = feature.properties;
if(GW2MapUtil.in_array(pane, ['region_poly', 'map_poly', 'sector_poly', 'task_poly', 'event_poly'])){
return {
pane: pane,
stroke: true,
opacity: 0.8,
color: this.options.colors[pane] || 'rgba(255, 255, 255, 0.3)',
weight: 2,
interactive: false,
}
}
return {
pane: pane,
stroke: true,
opacity: 0.8,
color: p.color || 'rgba(255, 255, 255, 0.3)',
weight: 3,
interactive: true,
}
}
/**
* @param {[*,*]} coords
* @returns {LatLng}
* @private
*/
_p2ll(coords){
return this.map.unproject(coords, this.options.maxZoom);
}
/**
* @param {[*,*]} coords
* @param {number} zoom
* @returns {[*,*]}
* @private
*/
_project(coords, zoom){
return coords.map(c => Math.floor((c / (1 << (this.options.maxZoom - zoom))) / 256));
}
/**
* @param {[*,*]} coords
* @param {number} zoom
* @returns {string}
* @private
*/
_tileGetter(coords, zoom){
let clamp = this.viewRect.map(c => this._project(c, zoom));
let ta = this.dataset.tileAdjust;
if(coords.x < clamp[0][0] - ta || coords.x > clamp[1][0] + ta || coords.y < clamp[0][1] - ta || coords.y > clamp[1][1] + ta){
return this.options.errorTile;
}
return this.options.tileBase
+ this.dataset.continentId + '/'
+ (this.dataset.customFloor || this.dataset.floorId) + '/'
+ zoom + '/' + coords.x + '/' + coords.y + this.options.tileExt;
}
}
/**
* Class GW2MapDataset
*
* reads the dataset from the container element, validates and stores the values in this.dataset
*
* i hate all of this.
*/
class GW2MapDataset{
//noinspection RegExpRedundantEscape
metadata = {
continentId : {type: 'int', default: 1},
floorId : {type: 'int', default: 1},
regionId : {type: 'int', default: null},
mapId : {type: 'int', default: null},
customFloor : {type: 'int', default: null},
language : {type: 'int', default: null},
zoom : {type: 'int', default: -1},
tileAdjust : {type: 'int', default: 0},
mapControls : {type: 'bool', default: true},
linkbox : {type: 'bool', default: false},
events : {type: 'bool', default: false},
initLayers : {type: 'array', default: null, regex: /^([a-z_,\s]+)$/i},
extraLayers : {type: 'array', default: [], regex: /^([a-z_,\s]+)$/i},
centerCoords: {type: 'array', default: null, regex: /^([\[\]\s\d\.,]+)$/},
customRect : {type: 'array', default: null, regex: /^([\[\]\s\d\.,]+)$/},
includeMaps : {type: 'array', default: [], regex: /^([\s\d,]+)$/},
};
dataset = {};
/**
* @param {Object} dataset
* @param {Object} options
*/
constructor(dataset, options){
this.options = options;
this._parse(dataset);
}
/**
* @returns {Object}
*/
getData(){
return this.dataset;
}
/**
* @param {Object} dataset
* @private
*/
_parse(dataset){
Object.keys(this.metadata).forEach(k => {
if(typeof dataset[k] === 'undefined' || dataset[k] === ''){
this.dataset[k] = this.metadata[k].default;
}
else{
['int', 'bool', 'array', 'string'].forEach(t => {
if(this.metadata[k].type === t){
this.dataset[k] = this['_parse_'+t](dataset[k], this.metadata[k]);
}
});
}
if(typeof this['_parse_'+k] === 'function'){
this.dataset[k] = this['_parse_'+k](this.dataset[k], this.metadata[k]);
}
});
}
/**
* @param {Object} data
* @returns {number}
* @private
*/
_parse_int(data){
return GW2MapUtil.intval(data);
}
/**
* @param {Object} data
* @returns {boolean}
* @private
*/
_parse_bool(data){
return GW2MapUtil.in_array(data.toLowerCase(), ['1', 'true', 't', 'yes', 'y']);
}
/**
* @param {Object} data
* @param {Object} meta
* @returns {*}
* @private
*/
_parse_array(data, meta){
let match = data.match(meta.regex);
if(match){
return match
}
return meta.default;
}
/**
* @param {Object} data
* @param {Object} meta
* @returns {*}
* @private
*/
_parse_string(data, meta){
return this._parse_array(data, meta);
}
/**
* @param {Object} data
* @param {Object} meta
* @returns {number}
* @private
*/
_parse_continentId(data, meta){
return GW2MapUtil.in_array(data, [1, 2]) ? data : meta.default;
}
/**
* @param {Object} data
* @param {Object} meta
* @returns {number}
* @private
*/
_parse_regionId(data, meta){
return data > 0 ? data : meta.default;
}
/**
* @param {Object} data
* @param {Object} meta
* @returns {number}
* @private
*/
_parse_mapId(data, meta){
return data > 0 ? data : meta.default;
}
/**
* @param {Object} data
* @param {Object} meta
* @returns {string}
* @private
*/
_parse_language(data, meta){
return ['de', 'en', 'es', 'fr', 'zh'][data] || this.options.lang;
};
/**
* @param {Object} data
* @returns {number}
* @private
*/
_parse_zoom(data){
return data < this.options.minZoom || data > this.options.maxZoom ? this.options.defaultZoom : data
}
/**
* @param {Object} data
* @param {Object} meta
* @returns {[]}
* @private
*/
_parse_includeMaps(data, meta){
if(data === meta.default){
return data;
}
let ret = [];
data[0].replace(/[^\d,]/g, '').split(',').forEach(v => {
if(v){
ret.push(GW2MapUtil.intval(v));
}
});
return ret
}
/**
* @param {Object} data
* @param {Object} meta
* @returns {number[][]}
* @private
*/
_parse_customRect(data, meta){
if(data === meta.default){
return data;
}
data = JSON.parse(data[0]);
if(data.length < 2 || data[0].length < 2 || data[1].length < 2){
return meta.default;
}
return data;
}
/**
* @param {Object} data
* @param {Object} meta
* @returns {number[]}
* @private
*/
_parse_centerCoords(data, meta){
if(data === meta.default){
return data;
}
data = JSON.parse(data[0]);
if(data.length < 2 || typeof data[0] !== 'number' || typeof data[1] !== 'number'){
return meta.default;
}
return data;
}
/**
* @param {Object} data
* @param {Object} meta
* @returns {string[]}
* @private
*/
_parse_extraLayers(data, meta){
if(data === meta.default){
return data;
}
let ret = [];
data[0].replace(/\s/g, '').split(',').forEach(v => {
if(v){
ret.push(v.toLowerCase());
}
});
return ret;
}
/**
* @param {Object} data
* @param {Object} meta
* @returns {string[]}
* @private
*/
_parse_initLayers(data, meta){
return this._parse_extraLayers(data, meta);
}
}
/**
* Class GW2MapUtil
*/
class GW2MapUtil{
/**
* @param {Object} target
* @param {Object} source
* @returns {Object}
*/
static extend(target, source) {
for(let property in source) {
if(source.hasOwnProperty(property)) {
target[property] = source[property];
}
}
return target;
}
/**
* @link http://locutus.io/php/var/intval/
*
* @param {*} mixed_var
* @param {number} base
* @returns {*}
*/
static intval(mixed_var, base){
let tmp;
let type = typeof(mixed_var);
if(type === 'boolean'){
return +mixed_var;
}
else if(type === 'string'){
tmp = parseInt(mixed_var, base || 10);
return (isNaN(tmp) || !isFinite(tmp)) ? 0 : tmp;
}
else if(type === 'number' && isFinite(mixed_var)){
return mixed_var|0;
}
else{
return 0;
}
}
/**
* @param {*} needle
* @param {*} haystack
* @returns {boolean}
*/
static in_array(needle, haystack){
for(let key in haystack){
if(haystack.hasOwnProperty(key)){
if(haystack[key] === needle){
return true;
}
}
}
return false;
}
}
/**
* Class GW2GeoJSONAbstract
*/
class GW2GeoJSONAbstract{
featureCollections = {};
includeMaps = [];
constructor(includeMaps){
this.includeMaps = includeMaps;
}
/**
* @param {string} layer
* @param {string|number} id
* @param {number} mapID
* @param {string} name
* @param {*} properties
* @param {*} geometry
* @param {string} [geometryType]
* @returns {GW2FloorGeoJSON}
* @protected
*/
_addFeature(layer, id, mapID, name, properties, geometry, geometryType){
if(!this.featureCollections[layer]){
this.featureCollections[layer] = new GeoJSONFeatureCollection();
}
this.featureCollections[layer]
.addFeature(GW2MapUtil.extend({
name : name,
mapID : mapID,
layertype: 'icon',
}, properties))
.setID(id)
.setGeometry(geometry, geometryType)
;
return this;
}
}
/**
* Class GW2FloorGeoJSON
*
* polyfill for https://github.com/arenanet/api-cdi/pull/62
*/
class GW2FloorGeoJSON extends GW2GeoJSONAbstract{
floordata = {};
maps = [];
/**
* GW2FloorGeoJSON constructor
*
* @param {*} floordata
* @param {[[],[]]} customRect
* @param {string[]} extraMarkers
* @param {number[]} includeMaps
*/
constructor(floordata, customRect, extraMarkers, includeMaps){
super(includeMaps);
this.floordata = floordata;
this.extraMarkers = ['adventure_icon', 'jumpingpuzzle_icon', 'polylines'].concat(extraMarkers);
this.setView(customRect);
}
/**
* @returns {GW2FloorGeoJSON}
*/
setView(customRect){
if(customRect){
this.viewRect = customRect; // @todo
}
else if(this.floordata.continent_rect){
this.viewRect = this.floordata.continent_rect;
}
else if(this.floordata.clamped_view){
this.viewRect = this.floordata.clamped_view;
}
else if(this.floordata.texture_dims){
this.viewRect = [[0, 0], this.floordata.texture_dims];
}
else{
this.viewRect = [[0, 0], [49152, 49152]];
}
return this;
}
/**
* @returns {*}
*/
getData(){
// a response to floors
if(this.floordata.regions){
this.continent(this.floordata.regions);
}
// a regions response
else if(this.floordata.maps){
this.region(this.floordata);
}
// an actual map response
else if(this.floordata.points_of_interest){
this.map(this.floordata);
}
return {
viewRect: this.viewRect,
featureCollections: this.featureCollections,
};
}
/**
* @param {*} continent
* @returns {GW2FloorGeoJSON}
*/
continent(continent){
Object.keys(continent).forEach(regionID => this.region(continent[regionID]));
return this;
}
/**
* @param {*} region
* @returns {GW2FloorGeoJSON}
*/
region(region){
this._addFeature('region_label', region.id, -1, region.name, {
type : 'region',
layertype: 'label',
}, region.label_coord);
/*
this._addFeature('region_poly', region.id, -1, region.name, {
type : 'region',
layertype: 'poly',
}, new GW2ContinentRect(region.continent_rect).getPoly(), 'Polygon');
*/
Object.keys(region.maps).forEach(mapID => {
let map = region.maps[mapID];
map.id = GW2MapUtil.intval(mapID);
// console.log('map', map.id, map.name);
// @todo
if(this.includeMaps.length > 0){
if(!GW2MapUtil.in_array(map.id, this.includeMaps)){
return this;
}
}
this.map(map);
});
return this;
}
/**
* @param {*} map
* @returns {GW2FloorGeoJSON}
*/
map(map){
this.maps.push(map.id);
let rect = new GW2ContinentRect(map.continent_rect);
// https://github.com/arenanet/api-cdi/issues/334
this._addFeature('map_label', map.id, map.id, map.name, {
min_level : map.min_level,
max_level : map.max_level,
type : 'map',
layertype : 'label',
}, map.label_coord || rect.getCenter());
/*
this._addFeature('map_poly', map.id, map.id, map.name, {
type : 'map',
layertype: 'poly',
}, rect.getPoly(), 'Polygon');
*/
this
.sectors(map.sectors, map.id)
.poi(map.points_of_interest, map.id)
.task(map.tasks, map.id)
.heropoint(map.skill_challenges, map.id)
.masteryPoint(map.mastery_points, map.id)
.adventure(map.adventures || [], map.id)
;
if(this.extraMarkers.length){
this.extraMarkers.forEach(layer => {
if(!GW2W_EXTRA_DATA[layer] || !GW2W_EXTRA_DATA[layer].data[map.id]){
return;
}
this.extra(GW2W_EXTRA_DATA[layer], layer, map.id);
});
}
return this;
}
/**
* @param {*} extra
* @param {string} layer
* @param {number} mapID
* @returns {GW2FloorGeoJSON}
*/
extra(extra, layer, mapID){
extra.data[mapID].forEach(e => {
this._addFeature(layer, e.id, mapID, (e.name || extra.name), {
icon : e.icon || extra.icon || null,
className : extra.className,
type : extra.type,
color : e.color || extra.color,
layertype : extra.layertype || 'icon',
description: e.description || extra.description || null
}, e.coord, (e.featureType ||extra.featureType || 'Point'));
});
}
/**
* @param {*} sectors
* @param {number} mapID
* @returns {GW2FloorGeoJSON}
*/
sectors(sectors, mapID){
Object.keys(sectors).forEach(sectorId =>{
let sector = sectors[sectorId];
if(GW2W_SECTOR_NAMES[sectorId]){
sector = GW2MapUtil.extend(sector, GW2W_SECTOR_NAMES[sectorId]);
}
this._addFeature('sector_label', sector.id, mapID, sector.name, {
chat_link: sector.chat_link,
level : sector.level,
type : 'sector',
layertype: 'label',
}, sector.coord);
this._addFeature('sector_poly', sector.id, mapID, sector.name, {
type : 'sector',
layertype: 'poly',
}, [sector.bounds], 'Polygon');
});
return this;
}
/**
* @param {*} pois
* @param {number} mapID
* @returns {GW2FloorGeoJSON}
*/
poi(pois, mapID){
Object.keys(pois).forEach(poiID =>{
let poi = pois[poiID];
if(GW2W_POIDATA[poi.type] && GW2W_POIDATA[poi.type][poiID]){
poi = GW2MapUtil.extend(poi, GW2W_POIDATA[poi.type][poiID]);
}
this._addFeature(poi.type + '_icon', poi.id || null, mapID, null, {
name : poi.name || poi.id || '',
type : poi.type,
chat_link: poi.chat_link || false,
// floor : poi.floor, // ???
icon : poi.icon
}, poi.coord);
});
return this;
}
/**
* @param {*} tasks
* @param {number} mapID
* @returns {GW2FloorGeoJSON}
*/
task(tasks, mapID){
Object.keys(tasks).forEach(taskID =>{
let task = tasks[taskID];
this._addFeature('task_icon', task.id, mapID, task.objective, {
chat_link: task.chat_link,
level : task.level,
type : 'task',
}, task.coord);
this._addFeature('task_poly', task.id, mapID, task.objective, {
type : 'task',
layertype: 'poly',
}, [task.bounds], 'Polygon');
});
return this;
}
/**
* @param {*} heropoints
* @param {number} mapID
* @returns {GW2FloorGeoJSON}
*/
heropoint(heropoints, mapID){
if(!heropoints.length){
return this;
}
heropoints.forEach(heropoint =>{
// https://github.com/arenanet/api-cdi/issues/329
this._addFeature('heropoint_icon', heropoint.id, mapID, null, {
name : GW2W_HEROPOINT_NAMES[heropoint.id] || '',
type : 'heropoint',
}, heropoint.coord)
});
return this;
}
/**
* @param {*} masterypoints
* @param {number} mapID
* @returns {GW2FloorGeoJSON}
*/
masteryPoint(masterypoints, mapID){
if(!masterypoints.length){
return this;
}
masterypoints.forEach(masterypoint =>{
this._addFeature('masterypoint_icon', masterypoint.id, mapID, null, {
name : GW2W_MASTERYPOINT_NAMES[masterypoint.id] || '',
region : masterypoint.region,
type : 'masterypoint',
}, masterypoint.coord)
});
return this;
}
/**
* @param {*} adventures
* @param {number} mapID
* @returns {GW2FloorGeoJSON}
*/
adventure(adventures, mapID){
if(!adventures.length){
return this;
}
adventures.forEach(adventure =>{
this._addFeature('adventure_icon', null, mapID, adventure.name, {
description: adventure.description || '',
type : 'adventure',
}, adventure.coord);
});
return this;
}
}
/**
* Class GW2EventGeoJSON
*/
class GW2EventGeoJSON extends GW2GeoJSONAbstract{
event_details = {};
map_details = {};
map = {};
constructor(event_details, map_details, includeMaps){
super(includeMaps);
this.event_details = event_details;
this.map_details = map_details;
}
getData(){
Object.keys(this.event_details).forEach(id => {
let event = this.event_details[id];
if(!GW2MapUtil.in_array(event.map_id, this.includeMaps)){
delete this.event_details[id];
delete this.map_details[event.map_id];
return;
}
let map = this.map_details[event.map_id];
if(!this.map[event.map_id]){
this.map[event.map_id] = map;
this.map[event.map_id].rect = new GW2ContinentRect(map.continent_rect, map.map_rect);
}
map = this.map[event.map_id];
this._addFeature('event_icon', id, event.map_id, event.name, {
icon : event.icon ? 'https://render.guildwars2.com/file/'+event.icon.signature+'/'+event.icon.file_id+'.png' : null,
flags : event.flags,
type : 'event',
layertype: 'icon',
}, map.rect.scaleCoords(event.location.center));
if(event.location.type === 'poly'){
this._addFeature('event_poly', id, event.map_id, event.name, {
type : 'event',
layertype: 'poly',
}, [event.location.points.map(point => map.rect.scaleCoords(point))], 'Polygon');
}
else{
this._addFeature('event_poly', id, event.map_id, event.name, {
type : 'event',
layertype: 'poly',
radius : map.rect.scaleLength(event.location.radius),
}, map.rect.scaleCoords(event.location.center), 'Point');
}
});
return {
featureCollections: this.featureCollections,
};
}
}
/**
* Class GW2ContinentRect
*/
class GW2ContinentRect{
/**
* GW2ContinentRect constructor
*
* @param continent_rect
* @param map_rect
*/
constructor(continent_rect, map_rect){
this.rect = continent_rect;
this.map_rect = map_rect;
}
/**
* returns bounds for L.LatLngBounds()
*
* @returns {*[]}
*/
getBounds(){
return [
[this.rect[0][0], this.rect[1][1]],
[this.rect[1][0], this.rect[0][1]]
]
}
/**
* returns the center of the rectangle
*
* @returns {*[]}
*/
getCenter(){
return [
(this.rect[0][0] + this.rect[1][0]) / 2,
(this.rect[0][1] + this.rect[1][1]) / 2
]
}
/**
* returns a polygon made of the rectangles corners
*
* @returns {*[]}
*/
getPoly(){
return [[
[this.rect[0][0], this.rect[0][1]],
[this.rect[1][0], this.rect[0][1]],
[this.rect[1][0], this.rect[1][1]],
[this.rect[0][0], this.rect[1][1]]
]]
}
/**
* @param {[]} coords from event_details.json or Mumble Link data.
* @param {[]} [map_rect] taken from maps.json or map_floor.json
* @returns {*[]}
*/
scaleCoords(coords, map_rect){
map_rect = this.map_rect || map_rect;
return [
Math.round(this.rect[0][0]+(this.rect[1][0]-this.rect[0][0])*(coords[0]-map_rect[0][0])/(map_rect[1][0]-map_rect[0][0])),
Math.round(this.rect[0][1]+(this.rect[1][1]-this.rect[0][1])*(1-(coords[1]-map_rect[0][1])/(map_rect[1][1]-map_rect[0][1])))
]
}
/**
* @param {number} length from event_details.json or Mumble Link data
* @param {[]} [map_rect] taken from maps.json or map_floor.json
* @returns {number}
*/
scaleLength(length, map_rect){
// still unsure about the correct values here
length = length / (1/24);
map_rect = this.map_rect || map_rect;
let scalex = (length - map_rect[0][0]) / (map_rect[1][0] - map_rect[0][0]);
let scaley = (length - map_rect[0][1]) / (map_rect[1][1] - map_rect[0][1]);
return Math.sqrt((scalex * scalex) + (scaley * scaley));
}
}
/**
* Class GeoJSONFeatureCollection
*/
class GeoJSONFeatureCollection{
/**
* GeoJSONFeatureCollection constructor
*/
constructor(){
this.json = {
type: 'FeatureCollection',
features: [],
};
}
/**
* @returns {{type: string, features: Array}|*}
*/
getJSON(){
this.json.features.forEach((feature, i) => this.json.features[i] = feature.getJSON());
return this.json;
}
/**
* @param type
* @param properties
* @returns {GeoJSONFeatureCollection}
*/
setCRS(type, properties){
this.json.crs = {
type: type,
properties: properties,
};
return this;
}
/**
* @param properties
* @returns {GeoJSONFeature}
*/
addFeature(properties){
let feature = new GeoJSONFeature(properties);
this.json.features.push(feature);
return feature;
}
}
/**
* Class GeoJSONFeature
*/
class GeoJSONFeature{
/**
* GeoJSONFeature constructor
*
* @param properties
*/
constructor(properties){
this.json = {
type: 'Feature',
geometry: {
type : '',
coordinates: [],
},
properties: properties || {},
};
}
/**
* @returns {{type: string, geometry: {type: string, coordinates: Array}, properties: (*|{})}|*}
*/
getJSON(){
return this.json;
}
/**
* @param id
* @returns {GeoJSONFeature}
*/
setID(id){
if(id){
this.json.id = id; // gmaps
this.json.properties.id = id; // leaflet
}
return this;
}
/**
* @param coords
* @param type
* @returns {GeoJSONFeature}
*/
setGeometry(coords, type){
this.json.geometry.coordinates = coords;
this.json.geometry.type = GW2MapUtil.in_array(type, [
'Point', 'MultiPoint', 'LineString', 'MultiLineString', 'Polygon', 'MultiPolygon', 'GeometryCollection'
]) ? type : 'Point';
return this;
}
}
/**
* prototype DOM rewrite inc
* @link https://github.com/prototypejs/prototype/blob/master/src/prototype/dom/dom.js
*/
class PrototypeElement{
static addClassName(element, className){
if(!this.hasClassName(element, className)){
element.className += (element.className ? ' ' : '') + className;
}
return element;
}
static removeClassName(element, className){
element.className = element.className.replace(this.getRegExpForClassName(className), ' ').replace(/^\s+/, '').replace(/\s+$/, '');
return element;
}
static toggleClassName(element, className, bool) {
if(typeof bool === 'undefined'){
bool = !this.hasClassName(element, className);
}
return this[bool ? 'addClassName' : 'removeClassName'](element, className);
}
static hasClassName(element, className){
let elementClassName = element.className;
if(elementClassName.length === 0){
return false;
}
if(elementClassName === className){
return true;
}
return this.getRegExpForClassName(className).test(elementClassName);
}
static getRegExpForClassName(className){
return new RegExp("(^|\\s+)" + className + "(\\s+|$)");
}
}
// invoke the maps
(($options, $containers) => {
$containers = $containers || document.getElementsByClassName($options.containerClassName);
// no map, no scripts.
if(!$containers.length){
return;
}
$options = GW2MapUtil.extend({
containerClassName: 'gw2map',
linkboxClassName : 'gw2map-linkbox',
navClassName : 'gw2map-nav',
scriptContainerId : 'gw2map-script',
scripts:[
'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.5.1/leaflet-src.js',
// 'https://unpkg.com/leaflet-ant-path@1.3.0/dist/leaflet-ant-path.js',
'https://wiki.guildwars2.com/index.php?title=Widget:Map_floors/data&action=raw&ctype=text/javascript', ],
stylesheets: [
'https://wiki.guildwars2.com/index.php?title=Widget:Map_floors/style&action=raw&ctype=text/css',
],
}, $options);
// scripts to
$options.scripts.forEach(script => {
let s = document.getElementById($options.scriptContainerId);
let node = document.createElement('script');
node.src = script;
s.parentNode.insertBefore(node, s);
});
// stylesheets to the
$options.stylesheets.forEach(stylesheet => {
let node = document.createElement('link');
node.rel = 'stylesheet';
node.href = stylesheet;
document.getElementsByTagName('head')[0].appendChild(node);
});
// ogogog
window.addEventListener('load', () => {
// check if leaflet is loaded (paranoid)
if(typeof L === 'undefined' || !L.version){
console.log('GW2Map error: leaflet not loaded!');
return;
}
// https://github.com/Leaflet/Leaflet.fullscreen
L.Control.Fullscreen = L.Control.extend({
options: {
position: 'topleft',
title : {
'false': 'View Fullscreen',
'true' : 'Exit Fullscreen',
},
},
onAdd: function(map){
let container = L.DomUtil.create('div', 'leaflet-control-fullscreen leaflet-bar leaflet-control');
this.link = L.DomUtil.create('a', 'leaflet-control-fullscreen-button leaflet-bar-part', container);
this.link.href = '#';
this._map = map;
this._map.on('fullscreenchange', this._toggleTitle, this);
this._toggleTitle();
L.DomEvent.on(this.link, 'click', this._click, this);
return container;
},
_click: function(e){
L.DomEvent.stopPropagation(e);
L.DomEvent.preventDefault(e);
this._map.toggleFullscreen(this.options);
},
_toggleTitle: function(){
this.link.title = this.options.title[this._map.isFullscreen()];
},
});
L.Map.include({
isFullscreen: function(){
return this._isFullscreen || false;
},
toggleFullscreen: function(options){
let container = this.getContainer();
if(this.isFullscreen()){
if(options && options.pseudoFullscreen){
this._disablePseudoFullscreen(container);
}
else if(document.exitFullscreen){
document.exitFullscreen();
}
else if(document.mozCancelFullScreen){
document.mozCancelFullScreen();
}
else if(document.webkitCancelFullScreen){
document.webkitCancelFullScreen();
}
else if(document.msExitFullscreen){
document.msExitFullscreen();
}
else{
this._disablePseudoFullscreen(container);
}
}
else{
if(options && options.pseudoFullscreen){
this._enablePseudoFullscreen(container);
}
else if(container.requestFullscreen){
container.requestFullscreen();
}
else if(container.mozRequestFullScreen){
container.mozRequestFullScreen();
}
else if(container.webkitRequestFullscreen){
container.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
}
else if(container.msRequestFullscreen){
container.msRequestFullscreen();
}
else{
this._enablePseudoFullscreen(container);
}
}
},
_enablePseudoFullscreen: function(container){
L.DomUtil.addClass(container, 'leaflet-pseudo-fullscreen');
this._setFullscreen(true);
this.fire('fullscreenchange');
},
_disablePseudoFullscreen: function(container){
L.DomUtil.removeClass(container, 'leaflet-pseudo-fullscreen');
this._setFullscreen(false);
this.fire('fullscreenchange');
},
_setFullscreen: function(fullscreen){
this._isFullscreen = fullscreen;
let container = this.getContainer();
if(fullscreen){
L.DomUtil.addClass(container, 'leaflet-fullscreen-on');
}
else{
L.DomUtil.removeClass(container, 'leaflet-fullscreen-on');
}
this.invalidateSize();
},
_onFullscreenChange: function(e){
let fullscreenElement =
document.fullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement ||
document.msFullscreenElement;
if(fullscreenElement === this.getContainer() && !this._isFullscreen){
this._setFullscreen(true);
this.fire('fullscreenchange');
}
else if(fullscreenElement !== this.getContainer() && this._isFullscreen){
this._setFullscreen(false);
this.fire('fullscreenchange');
}
},
});
L.Map.mergeOptions({fullscreenControl: false});
L.Map.addInitHook(function(){
if(this.options.fullscreenControl){
this.fullscreenControl = new L.Control.Fullscreen(this.options.fullscreenControl);
this.addControl(this.fullscreenControl);
}
let fullscreenchange;
if('onfullscreenchange' in document){
fullscreenchange = 'fullscreenchange';
}
else if('onmozfullscreenchange' in document){
fullscreenchange = 'mozfullscreenchange';
}
else if('onwebkitfullscreenchange' in document){
fullscreenchange = 'webkitfullscreenchange';
}
else if('onmsfullscreenchange' in document){
fullscreenchange = 'MSFullscreenChange';
}
if(fullscreenchange){
let onFullscreenChange = L.bind(this._onFullscreenChange, this);
this.whenReady(function(){
L.DomEvent.on(document, fullscreenchange, onFullscreenChange);
});
this.on('unload', function(){
L.DomEvent.off(document, fullscreenchange, onFullscreenChange);
});
}
});
L.control.fullscreen = function(options){
return new L.Control.Fullscreen(options);
};
// coordinate view with selectable input (eases gw2wiki use)
L.Control.Coordview = L.Control.extend({
options: {
position: 'bottomleft',
},
onAdd: function(map){
let container = L.DomUtil.create('div', 'leaflet-control-coordview leaflet-control');
let input = L.DomUtil.create('input');
input.type = 'text';
input.placeholder = '