0

Merge #1311 into master

This commit is contained in:
Andrew Chin
2018-05-06 13:47:17 -04:00
29 changed files with 1355 additions and 3124 deletions

View File

@@ -18,7 +18,7 @@ Blog:
The Minecraft Overviewer is a command-line tool for rendering high-resolution The Minecraft Overviewer is a command-line tool for rendering high-resolution
maps of Minecraft worlds. It generates a set of static html and image files and maps of Minecraft worlds. It generates a set of static html and image files and
uses the Google Maps API to display a nice interactive map. uses Leaflet to display a nice interactive map.
The Overviewer has been in active development for several years and has many The Overviewer has been in active development for several years and has many
features, including day and night lighting, cave rendering, mineral overlays, features, including day and night lighting, cave rendering, mineral overlays,
@@ -66,16 +66,9 @@ Viewing the Results
------------------- -------------------
Within the output directory you will find two things: an index.html file, and a Within the output directory you will find two things: an index.html file, and a
directory hierarchy full of images. To view your world, simply open index.html directory hierarchy full of images. To view your world, simply open index.html
in a web browser. Internet access is required to load the Google Maps API in a web browser.
files, but you otherwise don't need anything else.
You can throw these files up to a web server to let others view your map. To You can throw these files up to a web server to let others view your map.
ensure the map works when viewed through the Internet, you'll need a Google
Maps API key, which you can `get for free from Google`_. Please note that you
are also bound by the `Google Maps API Terms of Service`_.
.. _get for free from Google: https://developers.google.com/maps/documentation/javascript/get-api-key
.. _Google Maps API Terms of Service: https://developers.google.com/maps/terms
Bugs Bugs
==== ====

View File

@@ -25,8 +25,8 @@ Background Info
=============== ===============
The Overviewer's task is to take Minecraft worlds and render them into a set of The Overviewer's task is to take Minecraft worlds and render them into a set of
tiles that can be displayed with a Google Maps interface. This section goes over tiles that can be displayed with a Leaflet interface. This section goes over how
how Minecraft worlds work and are stored. Minecraft worlds work and are stored.
A Minecraft world extends indefinitely along the two horizontal axes, and are A Minecraft world extends indefinitely along the two horizontal axes, and are
exactly 256 units high. Minecraft worlds are made of voxels (volumetric pixels), exactly 256 units high. Minecraft worlds are made of voxels (volumetric pixels),
@@ -455,12 +455,12 @@ quickly become too much data to handle at once. (Early versions of the
Overviewer did this, but the large, unwieldy images quickly motivated the Overviewer did this, but the large, unwieldy images quickly motivated the
development of rendering to individual tiles). development of rendering to individual tiles).
Hence choosing a technology like Google Maps, which draws small tiles together Hence choosing a technology like Google Maps or Leaflet, which draws small
to make it look like one large image, lets rendering even the largest worlds tiles together to make it look like one large image, lets rendering even the
possible. The Overviewer can draw each tile separately and not have to load the largest worlds possible. The Overviewer can draw each tile separately and not
entire map into memory at once. The next sections describe how to determine have to load the entire map into memory at once. The next sections describe
which chunks to render in which tiles, and how to reason about tile ↔ chunk how to determine which chunks to render in which tiles, and how to reason
mappings. about tile ↔ chunk mappings.
Tile Layout Tile Layout
----------- -----------

View File

@@ -19,10 +19,10 @@ supporting mod blocks is not trivial.
Can I view Overviewer maps without having an internet connection? Can I view Overviewer maps without having an internet connection?
----------------------------------------------------------------- -----------------------------------------------------------------
Not at the moment. The Overviewer relies on the Google maps API to display Yes, absolutely. The Overviewer switched away from the Google Maps API and
maps, which your browser needs to load from Google. However, switching away now uses Leaflet. All files which Overviewer needs are included in the output,
from Google Maps is something that will most likely be looked into in the so even if you have no internet connection, you will still be able to view the
future. map without any issues.
When my map expands, I see remnants of another zoom level When my map expands, I see remnants of another zoom level
--------------------------------------------------------- ---------------------------------------------------------

View File

@@ -19,7 +19,7 @@ Introduction
============ ============
The Minecraft Overviewer is a command-line tool for rendering high-resolution The Minecraft Overviewer is a command-line tool for rendering high-resolution
maps of Minecraft worlds. It generates a set of static html and image files and maps of Minecraft worlds. It generates a set of static html and image files and
uses the Google Maps API to display a nice interactive map. uses Leaflet to display a nice interactive map.
The Overviewer has been in active development for several years and has many The Overviewer has been in active development for several years and has many
features, including day and night lighting, cave rendering, mineral overlays, features, including day and night lighting, cave rendering, mineral overlays,
@@ -64,7 +64,7 @@ Features
* Choose from four rendering angles. * Choose from four rendering angles.
* Generates a Google Maps powered map! * Generates a Leaflet powered map!
* Runs on Linux, Windows, and Mac platforms! * Runs on Linux, Windows, and Mac platforms!

View File

@@ -27,6 +27,7 @@ import world
import util import util
from files import FileReplacer, mirror_dir, get_fs_caps from files import FileReplacer, mirror_dir, get_fs_caps
class AssetManager(object): class AssetManager(object):
"""\ """\
These objects provide an interface to metadata and persistent data, and at the These objects provide an interface to metadata and persistent data, and at the
@@ -36,9 +37,9 @@ There should only be one instances of these per execution.
def __init__(self, outputdir, custom_assets_dir=None): def __init__(self, outputdir, custom_assets_dir=None):
"""\ """\
Initializes the AssetManager with the top-level output directory. Initializes the AssetManager with the top-level output directory.
It can read/parse and write/dump the overviewerConfig.js file into this top-level It can read/parse and write/dump the overviewerConfig.js file into this
directory. top-level directory.
""" """
self.outputdir = outputdir self.outputdir = outputdir
self.custom_assets_dir = custom_assets_dir self.custom_assets_dir = custom_assets_dir
@@ -47,13 +48,16 @@ directory.
self.fs_caps = get_fs_caps(self.outputdir) self.fs_caps = get_fs_caps(self.outputdir)
# look for overviewerConfig in self.outputdir # look for overviewerConfig in self.outputdir
config_loc = os.path.join(self.outputdir, "overviewerConfig.js")
try: try:
with open(os.path.join(self.outputdir, "overviewerConfig.js")) as c: with open(config_loc) as c:
overviewerConfig_str = "{" + "\n".join(c.readlines()[1:-1]) + "}" ovconf_str = "{" + "\n".join(c.readlines()[1:-1]) + "}"
self.overviewerConfig = json.loads(overviewerConfig_str) self.overviewerConfig = json.loads(ovconf_str)
except Exception, e: except Exception, e:
if os.path.exists(os.path.join(self.outputdir, "overviewerConfig.js")): if os.path.exists(config_loc):
logging.warning("A previous overviewerConfig.js was found, but I couldn't read it for some reason. Continuing with a blank config") logging.warning("A previous overviewerConfig.js was found, "
"but I couldn't read it for some reason."
"Continuing with a blank config")
logging.debug(traceback.format_exc()) logging.debug(traceback.format_exc())
self.overviewerConfig = dict(tilesets=dict()) self.overviewerConfig = dict(tilesets=dict())
@@ -73,7 +77,6 @@ directory.
if conf['path'] == name: if conf['path'] == name:
return conf return conf
return dict() return dict()
def initialize(self, tilesets): def initialize(self, tilesets):
"""Similar to finalize() but calls the tilesets' get_initial_data() """Similar to finalize() but calls the tilesets' get_initial_data()
@@ -92,26 +95,36 @@ directory.
def _output_assets(self, tilesets, initial): def _output_assets(self, tilesets, initial):
if not initial: if not initial:
get_data = lambda tileset: tileset.get_persistent_data() def get_data(tileset):
return tileset.get_persistent_data()
else: else:
get_data = lambda tileset: tileset.get_initial_data() def get_data(tileset):
return tileset.get_initial_data()
# dictionary to hold the overviewerConfig.js settings that we will dumps # dictionary to hold the overviewerConfig.js settings that we will dump
# to JSON using dumps
dump = dict() dump = dict()
dump['CONST'] = dict(tileSize=384) dump['CONST'] = dict(tileSize=384)
dump['CONST']['image'] = { dump['CONST']['image'] = {
'defaultMarker': 'signpost.png', 'defaultMarker': 'signpost.png',
'signMarker': 'signpost_icon.png', 'signMarker': 'signpost_icon.png',
'bedMarker': 'bed.png', 'bedMarker': 'bed.png',
'spawnMarker': 'https://google-maps-icons.googlecode.com/files/home.png', 'spawnMarker': 'icons/marker_home.png',
'queryMarker': 'https://google-maps-icons.googlecode.com/files/regroup.png' 'spawnMarker2x': 'icons/marker_home_2x.png',
} 'queryMarker': 'icons/marker_location.png',
'queryMarker2x': 'icons/marker_location_2x.png'
}
dump['CONST']['mapDivId'] = 'mcmap' dump['CONST']['mapDivId'] = 'mcmap'
dump['CONST']['regionStrokeWeight'] = 2 # Obselete dump['CONST']['UPPERLEFT'] = world.UPPER_LEFT
dump['CONST']['UPPERLEFT'] = world.UPPER_LEFT; dump['CONST']['UPPERRIGHT'] = world.UPPER_RIGHT
dump['CONST']['UPPERRIGHT'] = world.UPPER_RIGHT; dump['CONST']['LOWERLEFT'] = world.LOWER_LEFT
dump['CONST']['LOWERLEFT'] = world.LOWER_LEFT; dump['CONST']['LOWERRIGHT'] = world.LOWER_RIGHT
dump['CONST']['LOWERRIGHT'] = world.LOWER_RIGHT; dump['CONST']['image']['compass'] = {
world.UPPER_LEFT: 'compass_upper-left.png',
world.UPPER_RIGHT: 'compass_upper-right.png',
world.LOWER_LEFT: 'compass_lower-left.png',
world.LOWER_RIGHT: 'compass_lower-right.png'
}
# based on the tilesets we have, group them by worlds # based on the tilesets we have, group them by worlds
worlds = [] worlds = []
@@ -124,7 +137,7 @@ directory.
dump['map'] = dict() dump['map'] = dict()
dump['map']['debug'] = True dump['map']['debug'] = True
dump['map']['cacheTag'] = str(int(time.time())) dump['map']['cacheTag'] = str(int(time.time()))
dump['map']['north_direction'] = 'lower-left' # only temporary dump['map']['north_direction'] = 'lower-left' # only temporary
dump['map']['center'] = [-314, 67, 94] dump['map']['center'] = [-314, 67, 94]
dump['map']['controls'] = { dump['map']['controls'] = {
'pan': True, 'pan': True,
@@ -134,13 +147,10 @@ directory.
'mapType': True, 'mapType': True,
'overlays': True, 'overlays': True,
'coordsBox': True, 'coordsBox': True,
'searchBox': True # Lolwat. Obselete
} }
dump['tilesets'] = [] dump['tilesets'] = []
for tileset in tilesets: for tileset in tilesets:
dump['tilesets'].append(get_data(tileset)) dump['tilesets'].append(get_data(tileset))
@@ -152,38 +162,42 @@ directory.
# write out config # write out config
jsondump = json.dumps(dump, indent=4) jsondump = json.dumps(dump, indent=4)
with FileReplacer(os.path.join(self.outputdir, "overviewerConfig.js"), capabilities=self.fs_caps) as tmpfile: with FileReplacer(os.path.join(self.outputdir, "overviewerConfig.js"),
capabilities=self.fs_caps) as tmpfile:
with codecs.open(tmpfile, 'w', encoding='UTF-8') as f: with codecs.open(tmpfile, 'w', encoding='UTF-8') as f:
f.write("var overviewerConfig = " + jsondump + ";\n") f.write("var overviewerConfig = " + jsondump + ";\n")
#Copy assets, modify index.html # Copy assets, modify index.html
self.output_noconfig() self.output_noconfig()
def output_noconfig(self): def output_noconfig(self):
# copy web assets into destdir: # copy web assets into destdir:
global_assets = os.path.join(util.get_program_path(), "overviewer_core", "data", "web_assets") global_assets = os.path.join(util.get_program_path(),
"overviewer_core", "data", "web_assets")
if not os.path.isdir(global_assets): if not os.path.isdir(global_assets):
global_assets = os.path.join(util.get_program_path(), "web_assets") global_assets = os.path.join(util.get_program_path(), "web_assets")
mirror_dir(global_assets, self.outputdir, capabilities=self.fs_caps) mirror_dir(global_assets, self.outputdir, capabilities=self.fs_caps)
if self.custom_assets_dir: if self.custom_assets_dir:
# Could have done something fancy here rather than just overwriting # We could have done something fancy here rather than just
# the global files, but apparently this what we used to do pre-rewrite. # overwriting the global files, but apparently this what we used to
mirror_dir(self.custom_assets_dir, self.outputdir, capabilities=self.fs_caps) # do pre-rewrite.
mirror_dir(self.custom_assets_dir, self.outputdir,
# write a dummy baseMarkers.js if none exists capabilities=self.fs_caps)
if not os.path.exists(os.path.join(self.outputdir, "baseMarkers.js")):
with open(os.path.join(self.outputdir, "baseMarkers.js"), "w") as f:
f.write("// if you wants signs, please see genPOI.py\n");
# write a dummy baseMarkers.js if none exists
basemarkers_path = os.path.join(self.outputdir, "baseMarkers.js")
if not os.path.exists(basemarkers_path):
with open(basemarkers_path, "w") as f:
f.write("// if you wants signs, please see genPOI.py\n")
# create overviewer.js from the source js files # create overviewer.js from the source js files
js_src = os.path.join(util.get_program_path(), "overviewer_core", "data", "js_src") js_src = os.path.join(util.get_program_path(),
"overviewer_core", "data", "js_src")
if not os.path.isdir(js_src): if not os.path.isdir(js_src):
js_src = os.path.join(util.get_program_path(), "js_src") js_src = os.path.join(util.get_program_path(), "js_src")
with FileReplacer(os.path.join(self.outputdir, "overviewer.js"), capabilities=self.fs_caps) as tmpfile: with FileReplacer(os.path.join(self.outputdir, "overviewer.js"),
capabilities=self.fs_caps) as tmpfile:
with open(tmpfile, "w") as fout: with open(tmpfile, "w") as fout:
# first copy in js_src/overviewer.js # first copy in js_src/overviewer.js
with open(os.path.join(js_src, "overviewer.js"), 'r') as f: with open(os.path.join(js_src, "overviewer.js"), 'r') as f:
@@ -191,16 +205,20 @@ directory.
# now copy in the rest # now copy in the rest
for js in os.listdir(js_src): for js in os.listdir(js_src):
if not js.endswith("overviewer.js") and js.endswith(".js"): if not js.endswith("overviewer.js") and js.endswith(".js"):
with open(os.path.join(js_src,js)) as f: with open(os.path.join(js_src, js)) as f:
fout.write(f.read()) fout.write(f.read())
# Add time and version in index.html # Add time and version in index.html
indexpath = os.path.join(self.outputdir, "index.html") indexpath = os.path.join(self.outputdir, "index.html")
index = codecs.open(indexpath, 'r', encoding='UTF-8').read() index = codecs.open(indexpath, 'r', encoding='UTF-8').read()
index = index.replace("{title}", "Minecraft Overviewer") index = index.replace("{title}", "Minecraft Overviewer")
index = index.replace("{time}", time.strftime("%a, %d %b %Y %H:%M:%S %Z", time.localtime()).decode(self.preferredencoding)) index = index.replace("{time}",
versionstr = "%s (%s)" % (util.findGitVersion(), util.findGitHash()[:7]) time.strftime("%a, %d %b %Y %H:%M:%S %Z",
time.localtime())
.decode(self.preferredencoding))
versionstr = "%s (%s)" % (util.findGitVersion(),
util.findGitHash()[:7])
index = index.replace("{version}", versionstr) index = index.replace("{version}", versionstr)
with FileReplacer(indexpath, capabilities=self.fs_caps) as indexpath: with FileReplacer(indexpath, capabilities=self.fs_caps) as indexpath:

View File

@@ -1,47 +0,0 @@
overviewer.models = {};
/* WorldModel
* Primarily has a collection of TileSets
*/
overviewer.models.WorldModel = Backbone.Model.extend({
initialize: function(attrs) {
attrs.tileSets = new overviewer.models.TileSetCollection();
this.set(attrs);
}
});
/* WorldCollection
* A collection of WorldModels
*/
overviewer.models.WorldCollection = Backbone.Collection.extend({
model: overviewer.models.WorldModel
});
/* TileSetModel
*/
overviewer.models.TileSetModel = Backbone.Model.extend({
defaults: {
markers: []
},
initialize: function(attrs) {
// this implies that the Worlds collection must be
// initialized before any TIleSetModels are created
attrs.world = overviewer.collections.worlds.get(attrs.world);
this.set(attrs);
}
});
overviewer.models.TileSetCollection = Backbone.Collection.extend({
model: overviewer.models.TileSetModel
});
overviewer.models.GoogleMapModel = Backbone.Model.extend({
initialize: function(attrs) {
attrs.currentWorldView = overviewer.collections.worldViews[0];
this.set(attrs);
}
});

View File

@@ -11,8 +11,15 @@ var overviewer = {};
* This holds the map, probably the most important var in this file * This holds the map, probably the most important var in this file
*/ */
overviewer.map = null; overviewer.map = null;
overviewer.mapView = null; overviewer.worldCtrl = null;
overviewer.layerCtrl = null;
overviewer.compass = null;
overviewer.coord_box = null;
overviewer.current_world = null;
/// Records the current layer by name (if any) of each world
overviewer.current_layer = {};
overviewer.collections = { overviewer.collections = {
/** /**
@@ -29,6 +36,14 @@ overviewer.collections = {
*/ */
'infoWindow': null, 'infoWindow': null,
/**
* When switching regionsets, where should we zoom to?
* Defaults to spawn. Stored as map of world names to [latlng, zoom]
*/
'centers': {},
'overlays': {},
'worldViews': [], 'worldViews': [],
'haveSigns': false, 'haveSigns': false,
@@ -39,13 +54,14 @@ overviewer.collections = {
'markerInfo': {}, 'markerInfo': {},
/** /**
* holds a reference to the spawn marker. * holds a reference to the spawn marker.
*/ */
'spawnMarker': null, 'spawnMarker': null,
/** /**
* if a user visits a specific URL, this marker will point to the coordinates in the hash * if a user visits a specific URL, this marker will point to the
*/ * coordinates in the hash
*/
'locationMarker': null 'locationMarker': null
}; };
@@ -72,39 +88,3 @@ overviewer.classes = {
} }
}; };
overviewer.gmap = {
/**
* Generate a function to get the path to a tile at a particular location
* and zoom level.
*
* @param string path
* @param string pathBase
* @param string pathExt
*/
'getTileUrlGenerator': function(path, pathBase, pathExt) {
return function(tile, zoom) {
var url = path;
var urlBase = ( pathBase ? pathBase : '' );
if(tile.x < 0 || tile.x >= Math.pow(2, zoom) ||
tile.y < 0 || tile.y >= Math.pow(2, zoom)) {
url += '/blank';
} else if(zoom === 0) {
url += '/base';
} else {
for(var z = zoom - 1; z >= 0; --z) {
var x = Math.floor(tile.x / Math.pow(2, z)) % 2;
var y = Math.floor(tile.y / Math.pow(2, z)) % 2;
url += '/' + (x + 2 * y);
}
}
url = url + '.' + pathExt;
if(typeof overviewerConfig.map.cacheTag !== 'undefined') {
url += '?c=' + overviewerConfig.map.cacheTag;
}
return(urlBase + url);
};
}
};

View File

@@ -32,145 +32,296 @@ overviewer.util = {
* feature gets added. * feature gets added.
*/ */
'initialize': function() { 'initialize': function() {
overviewer.util.initializeClassPrototypes(); //overviewer.util.initializeClassPrototypes();
overviewer.util.initializePolyfills();
overviewer.util.initializeMarkers();
overviewer.collections.worlds = new overviewer.models.WorldCollection(); document.getElementById('NoJSWarning').remove();
$.each(overviewerConfig.worlds, function(index, el) { overviewer.coordBoxClass = L.Control.extend({
var n = new overviewer.models.WorldModel({name: el, id:el}); options: {
overviewer.collections.worlds.add(n); position: 'bottomleft',
}); },
initialize: function() {
this.coord_box = L.DomUtil.create('div', 'coordbox');
},
render: function(latlng) {
var currWorld = overviewer.current_world;
if (currWorld == null) {return;}
$.each(overviewerConfig.tilesets, function(index, el) { var currTileset = overviewer.current_layer[currWorld];
var newTset = new overviewer.models.TileSetModel(el); if (currTileset == null) {return;}
overviewer.collections.worlds.get(el.world).get("tileSets").add(newTset);
});
overviewer.collections.worlds.each(function(world, index, list) { var ovconf = currTileset.tileSetConfig;
var nv = new overviewer.views.WorldView({model: world});
overviewer.collections.worldViews.push(nv);
});
overviewer.mapModel = new overviewer.models.GoogleMapModel({}); w_coords = overviewer.util.fromLatLngToWorld(latlng.lat, latlng.lng, ovconf);
overviewer.mapView = new overviewer.views.GoogleMapView({el: document.getElementById(overviewerConfig.CONST.mapDivId), model:overviewer.mapModel});
// any controls must be created after the GoogleMapView is created var r_x = Math.floor(Math.floor(w_coords.x / 16.0) / 32.0);
// controls should be added in the order they should appear on screen, var r_z = Math.floor(Math.floor(w_coords.z / 16.0) / 32.0);
// with controls on the outside of the page being added first var r_name = "r." + r_x + "." + r_z + ".mca";
var compass = new overviewer.views.CompassView({tagName: 'DIV', model:overviewer.mapModel}); this.coord_box.innerHTML = "<strong>X</strong> " +
// no need to render the compass now. it's render event will get fired by Math.round(w_coords.x) +
// the maptypeid_chagned event " <strong>Z</strong> " + Math.round(w_coords.z) +
" (" + r_name + ")";
var coordsdiv = new overviewer.views.CoordboxView({tagName: 'DIV'}); },
coordsdiv.render(); onAdd: function() {
return this.coord_box;
var progressdiv = new overviewer.views.ProgressView({tagName: 'DIV'}); }
progressdiv.render();
progressdiv.updateProgress();
if (overviewer.collections.haveSigns) {
var signs = new overviewer.views.SignControlView();
signs.registerEvents(signs);
}
var overlayControl = new overviewer.views.OverlayControlView();
var spawnmarker = new overviewer.views.SpawnIconView();
// Update coords on mousemove
google.maps.event.addListener(overviewer.map, 'mousemove', function (event) {
coordsdiv.updateCoords(event.latLng);
}); });
google.maps.event.addListener(overviewer.map, 'idle', function (event) { overviewer.compassClass = L.Control.extend({
initialize: function(imagedict, options) {
L.Util.setOptions(this, options);
this.compass_img = L.DomUtil.create('img', 'compass');
this.imagedict = imagedict;
},
render: function(direction) {
this.compass_img.src = this.imagedict[direction];
},
onAdd: function() {
return this.compass_img;
}
});
overviewer.control = L.Control.extend({
initialize: function(options) {
L.Util.setOptions(this, options);
this.container = L.DomUtil.create('div', 'worldcontrol');
this.select = L.DomUtil.create('select');
this.select.onchange = this.onChange;
this.container.appendChild(this.select);
},
addWorld: function(world) {
var option = L.DomUtil.create('option');
option.value = world;
option.innerText = world;
this.select.appendChild(option);
},
onChange: function(ev) {
console.log(ev.target);
console.log(ev.target.value);
var selected_world = ev.target.value;
// save current view for the current_world
overviewer.collections.centers[overviewer.current_world][0] = overviewer.map.getCenter();
overviewer.collections.centers[overviewer.current_world][1] = overviewer.map.getZoom();
overviewer.layerCtrl.remove();
overviewer.layerCtrl = L.control.layers(
overviewer.collections.mapTypes[selected_world],
overviewer.collections.overlays[selected_world],
{collapsed: false})
.addTo(overviewer.map);
for (var world_name in overviewer.collections.mapTypes) {
for (var tset_name in overviewer.collections.mapTypes[world_name]) {
var lyr = overviewer.collections.mapTypes[world_name][tset_name];
if (world_name != selected_world) {
if (overviewer.map.hasLayer(lyr))
overviewer.map.removeLayer(lyr);
}
}
for (var tset_name in overviewer.collections.overlays[world_name]) {
var lyr = overviewer.collections.overlays[world_name][tset_name];
if (world_name != selected_world) {
if (overviewer.map.hasLayer(lyr))
overviewer.map.removeLayer(lyr);
}
}
}
var center = overviewer.collections.centers[selected_world];
overviewer.map.setView(center[0], center[1]);
overviewer.current_world = selected_world;
if (overviewer.collections.mapTypes[selected_world] && overviewer.current_layer[selected_world]) {
overviewer.map.addLayer(overviewer.collections.mapTypes[selected_world][overviewer.current_layer[selected_world].tileSetConfig.name]);
} else {
var tset_name = Object.keys(overviewer.collections.mapTypes[selected_world])[0]
overviewer.map.addLayer(overviewer.collections.mapTypes[selected_world][tset_name]);
}
},
onAdd: function() {
console.log("onAdd mycontrol");
return this.container
}
});
overviewer.map = L.map('mcmap', {
crs: L.CRS.Simple,
minZoom: 0});
overviewer.map.attributionControl.setPrefix(
'<a href="https://overviewer.org">Overviewer/Leaflet</a>');
overviewer.map.on('baselayerchange', function(ev) {
overviewer.current_layer[overviewer.current_world] = ev.layer;
var ovconf = ev.layer.tileSetConfig;
// Change the compass
overviewer.compass.render(ovconf.north_direction);
// Set the background colour
document.getElementById("mcmap").style.backgroundColor = ovconf.bgcolor;
if (overviewer.collections.locationMarker) {
overviewer.collections.locationMarker.remove();
}
// Remove old spawn marker, add new one
if (overviewer.collections.spawnMarker) {
overviewer.collections.spawnMarker.remove();
}
if (typeof(ovconf.spawn) == "object") {
var spawnIcon = L.icon({
iconUrl: overviewerConfig.CONST.image.spawnMarker,
iconRetinaUrl: overviewerConfig.CONST.image.spawnMarker2x,
iconSize: [32, 37],
iconAnchor: [15, 33],
});
var latlng = overviewer.util.fromWorldToLatLng(ovconf.spawn[0],
ovconf.spawn[1],
ovconf.spawn[2],
ovconf);
var ohaimark = L.marker(latlng, {icon: spawnIcon, title: "Spawn"});
ohaimark.on('click', function(ev) {
overviewer.map.setView(ev.latlng);
});
overviewer.collections.spawnMarker = ohaimark
overviewer.collections.spawnMarker.addTo(overviewer.map);
} else {
overviewer.collections.spawnMarker = null;
}
// reset the markers control with the markers for this layer
if (ovconf.marker_groups) {
console.log("markers for", ovconf.marker_groups);
markerCtrl = L.control.layers(
[],
ovconf.marker_groups, {collapsed: false}).addTo(overviewer.map);
}
overviewer.util.updateHash(); overviewer.util.updateHash();
}); });
google.maps.event.addListener(overviewer.map, 'maptypeid_changed', function(event) { overviewer.map.on('moveend', function(ev) {
// it's handy to keep track of the currently visible tileset. we let overviewer.util.updateHash();
// the GoogleMapView manage this });
overviewer.mapView.updateCurrentTileset();
var tset = overviewerConfig.tilesets[0];
compass.render();
spawnmarker.render();
if (overviewer.collections.locationMarker) {
overviewer.collections.locationMarker.setMap(null);
overviewer.collections.locationMarker = null;
}
// update list of spawn overlays
overlayControl.render();
// re-center on the last viewport
var currentWorldView = overviewer.mapModel.get("currentWorldView");
if (currentWorldView.options.lastViewport) {
var x = currentWorldView.options.lastViewport[0];
var y = currentWorldView.options.lastViewport[1];
var z = currentWorldView.options.lastViewport[2];
var zoom = currentWorldView.options.lastViewport[3];
var latlngcoords = overviewer.util.fromWorldToLatLng(x, y, z,
overviewer.mapView.options.currentTileSet);
overviewer.map.setCenter(latlngcoords);
if (zoom == 'max') {
zoom = overviewer.mapView.options.currentTileSet.get('maxZoom');
} else if (zoom == 'min') {
zoom = overviewer.mapView.options.currentTileSet.get('minZoom');
} else {
zoom = parseInt(zoom);
if (zoom < 0) {
// if zoom is negative, treat it as a "zoom out from max"
zoom += overviewer.mapView.options.currentTileSet.get('maxZoom');
} else {
// fall back to default zoom
zoom = overviewer.mapView.options.currentTileSet.get('defaultZoom');
}
}
// clip zoom
if (zoom > overviewer.mapView.options.currentTileSet.get('maxZoom'))
zoom = overviewer.mapView.options.currentTileSet.get('maxZoom');
if (zoom < overviewer.mapView.options.currentTileSet.get('minZoom'))
zoom = overviewer.mapView.options.currentTileSet.get('minZoom');
overviewer.map.setZoom(zoom);
}
overviewer.map.on("click", function(e) {
console.log(e.latlng);
var point = overviewer.util.fromLatLngToWorld(e.latlng.lat, e.latlng.lng, tset);
console.log(point);
}); });
var tilesetLayers = {}
// hook up some events overviewer.worldCtrl = new overviewer.control();
overviewer.compass = new overviewer.compassClass(
overviewer.mapModel.bind("change:currentWorldView", overviewer.mapView.render, overviewer.mapView); overviewerConfig.CONST.image.compass);
overviewer.coord_box = new overviewer.coordBoxClass();
overviewer.mapView.render();
// Jump to the hash if given (and do so for any further hash changes)
overviewer.util.initHash();
$(window).on('hashchange', function() { overviewer.util.initHash(); });
// create this control after initHash so it can correctly select the current world
var worldSelector = new overviewer.views.WorldSelectorView({tagName:'DIV'});
overviewer.collections.worlds.bind("add", worldSelector.render, worldSelector);
overviewer.util.initializeMarkers();
/* $.each(overviewerConfig.worlds, function(idx, world_name) {
overviewer.util.initializeMapTypes(); overviewer.collections.mapTypes[world_name] = {}
overviewer.util.initializeMap(); overviewer.collections.overlays[world_name] = {}
overviewer.util.initializeRegions(); overviewer.worldCtrl.addWorld(world_name);
overviewer.util.createMapControls();
*/
// run ready callbacks now
google.maps.event.addListenerOnce(overviewer.map, 'idle', function(){
// ok now..
overviewer.util.runReadyQueue();
overviewer.util.isReady = true;
}); });
overviewer.compass.addTo(overviewer.map);
overviewer.worldCtrl.addTo(overviewer.map);
overviewer.coord_box.addTo(overviewer.map);
overviewer.map.on('mousemove', function(ev) {
overviewer.coord_box.render(ev.latlng);
});
$.each(overviewerConfig.tilesets, function(idx, obj) {
var myLayer = new L.tileLayer('', {
tileSize: overviewerConfig.CONST.tileSize,
noWrap: true,
maxZoom: obj.maxZoom,
minZoom: obj.minZoom,
errorTileUrl: obj.base + obj.path + "/blank." + obj.imgextension,
});
myLayer.getTileUrl = overviewer.util.getTileUrlGenerator(obj.path, obj.base, obj.imgextension);
if (obj.isOverlay) {
overviewer.collections.overlays[obj.world][obj.name] = myLayer;
} else {
overviewer.collections.mapTypes[obj.world][obj.name] = myLayer;
}
obj.marker_groups = {};
if (overviewer.collections.haveSigns == true) {
// if there are markers for this tileset, create them now
if ((typeof markers !== 'undefined') && (obj.path in markers)) {
console.log("this tileset has markers:", obj);
for (var mkidx = 0; mkidx < markers[obj.path].length; mkidx++) {
var marker_group = new L.layerGroup();
var marker_entry = markers[obj.path][mkidx];
var icon = L.icon({iconUrl: marker_entry.icon,
iconSize: [32, 32]});
console.log("marker group:", marker_entry.displayName, marker_entry.groupName);
for (var dbidx = 0; dbidx < markersDB[marker_entry.groupName].raw.length; dbidx++) {
var db = markersDB[marker_entry.groupName].raw[dbidx];
var latlng = overviewer.util.fromWorldToLatLng(db.x, db.y, db.z, obj);
console.log(latlng);
marker_group.addLayer(new L.marker(latlng, {
icon: icon
}));
}
obj.marker_groups[marker_entry.displayName] = marker_group;
}
//var latlng = overviewer.util.fromWorldToLatLng(
// ovconf.spawn[0],
// ovconf.spawn[1],
// ovconf.spawn[2],
// obj);
//marker_group.addLayer(L.marker(
}
}
myLayer["tileSetConfig"] = obj;
if (typeof(obj.spawn) == "object") {
var latlng = overviewer.util.fromWorldToLatLng(obj.spawn[0], obj.spawn[1], obj.spawn[2], obj);
overviewer.collections.centers[obj.world] = [ latlng, 1 ];
} else {
overviewer.collections.centers[obj.world] = [ [0, 0], 1 ];
}
});
overviewer.layerCtrl = L.control.layers(
overviewer.collections.mapTypes[overviewerConfig.worlds[0]],
overviewer.collections.overlays[overviewerConfig.worlds[0]],
{collapsed: false})
.addTo(overviewer.map);
overviewer.current_world = overviewerConfig.worlds[0];
//myLayer.addTo(overviewer.map);
overviewer.map.setView(overviewer.util.fromWorldToLatLng(tset.spawn[0], tset.spawn[1], tset.spawn[2], tset), 1);
if (!overviewer.util.initHash()) {
overviewer.worldCtrl.onChange({target: {value: overviewer.current_world}});
}
}, },
'injectMarkerScript': function(url) { 'injectMarkerScript': function(url) {
@@ -180,10 +331,32 @@ overviewer.util = {
}, },
'initializeMarkers': function() { 'initializeMarkers': function() {
if (overviewer.collections.haveSigns=true) {
console.log("initializeMarkers");
//Object.keys(
//
}
return; return;
}, },
/** Any polyfills needed to improve browser compatibility
*/
'initializePolyfills': function() {
// From https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove
// IE is missing this
if (!('remove' in Element.prototype)) {
Element.prototype.remove = function() {
if (this.parentNode) {
this.parentNode.removeChild(this);
}
};
}
},
/** /**
* This adds some methods to these classes because Javascript is stupid * This adds some methods to these classes because Javascript is stupid
@@ -262,15 +435,6 @@ overviewer.util = {
"pregQuote": function(str) { "pregQuote": function(str) {
return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1"); return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
}, },
/**
* Change the map's div's background color according to the mapType's bg_color setting
*
* @param string mapTypeId
* @return string
*/
'getMapTypeBackgroundColor': function(id) {
return overviewerConfig.tilesets[id].bgcolor;
},
/** /**
* Gee, I wonder what this does. * Gee, I wonder what this does.
* *
@@ -316,10 +480,10 @@ overviewer.util = {
* *
* @return google.maps.LatLng * @return google.maps.LatLng
*/ */
'fromWorldToLatLng': function(x, y, z, model) { 'fromWorldToLatLng': function(x, y, z, tset) {
var zoomLevels = model.get("zoomLevels"); var zoomLevels = tset.zoomLevels;
var north_direction = model.get('north_direction'); var north_direction = tset.north_direction;
// the width and height of all the highest-zoom tiles combined, // the width and height of all the highest-zoom tiles combined,
// inverted // inverted
@@ -367,7 +531,7 @@ overviewer.util = {
// add on 12 px to the X coordinate to center our point // add on 12 px to the X coordinate to center our point
lng += 12 * perPixel; lng += 12 * perPixel;
return new google.maps.LatLng(lat, lng); return [-lat*overviewerConfig.CONST.tileSize, lng*overviewerConfig.CONST.tileSize]
}, },
/** /**
* The opposite of fromWorldToLatLng * The opposite of fromWorldToLatLng
@@ -379,9 +543,15 @@ overviewer.util = {
* *
* @return Array * @return Array
*/ */
'fromLatLngToWorld': function(lat, lng, model) { 'fromLatLngToWorld': function(lat, lng, tset) {
var zoomLevels = model.get("zoomLevels"); var zoomLevels = tset.zoomLevels;
var north_direction = model.get("north_direction"); var north_direction = tset.north_direction;
lat = -lat/overviewerConfig.CONST.tileSize;
lng = lng/overviewerConfig.CONST.tileSize;
// lat lng will always be between (0,0) -- top left corner
// (-384, 384) -- bottom right corner
// Initialize world x/y/z object to be returned // Initialize world x/y/z object to be returned
var point = Array(); var point = Array();
@@ -482,52 +652,42 @@ overviewer.util = {
overviewer.util.goToHash(); overviewer.util.goToHash();
// Clean up the hash. // Clean up the hash.
overviewer.util.updateHash(); overviewer.util.updateHash();
return true;
} else {
return false; // signal to caller that we didn't goto any hash
} }
} }
}, },
'setHash': function(x, y, z, zoom, w, maptype) { 'setHash': function(x, y, z, zoom, w, maptype) {
// save this info is a nice easy to parse format // save this info is a nice easy to parse format
var currentWorldView = overviewer.mapModel.get("currentWorldView"); var newHash = "#/" + Math.floor(x) + "/" + Math.floor(y) + "/" + Math.floor(z) + "/" + zoom + "/" + encodeURI(w) + "/" + encodeURI(maptype);
currentWorldView.options.lastViewport = [x,y,z,zoom];
var newHash = "#/" + Math.floor(x) + "/" + Math.floor(y) + "/" + Math.floor(z) + "/" + zoom + "/" + w + "/" + maptype;
overviewer.util.lastHash = newHash; // this should not trigger initHash overviewer.util.lastHash = newHash; // this should not trigger initHash
window.location.replace(newHash); window.location.replace(newHash);
}, },
'updateHash': function() { 'updateHash': function() {
var currTileset = overviewer.mapView.options.currentTileSet; // name of current world
var currWorld = overviewer.current_world;
if (currWorld == null) {return;}
var currTileset = overviewer.current_layer[currWorld];
if (currTileset == null) {return;} if (currTileset == null) {return;}
var coordinates = overviewer.util.fromLatLngToWorld(overviewer.map.getCenter().lat(),
overviewer.map.getCenter().lng(), var ovconf = currTileset.tileSetConfig;
currTileset);
var coordinates = overviewer.util.fromLatLngToWorld(overviewer.map.getCenter().lat,
overviewer.map.getCenter().lng,
ovconf);
var zoom = overviewer.map.getZoom(); var zoom = overviewer.map.getZoom();
var maptype = overviewer.map.getMapTypeId();
// convert mapType into a index if (zoom >= ovconf.maxZoom) {
var currentWorldView = overviewer.mapModel.get("currentWorldView");
var maptypeId = -1;
for (id in currentWorldView.options.mapTypeIds) {
if (currentWorldView.options.mapTypeIds[id] == maptype) {
maptypeId = id;
}
}
var worldId = -1;
for (id in overviewer.collections.worldViews) {
if (overviewer.collections.worldViews[id] == currentWorldView) {
worldId = id;
}
}
if (zoom >= currTileset.get('maxZoom')) {
zoom = 'max'; zoom = 'max';
} else if (zoom <= currTileset.get('minZoom')) { } else if (zoom <= ovconf.minZoom) {
zoom = 'min'; zoom = 'min';
} else { } else {
// default to (map-update friendly) negative zooms // default to (map-update friendly) negative zooms
zoom -= currTileset.get('maxZoom'); zoom -= ovconf.maxZoom;
} }
overviewer.util.setHash(coordinates.x, coordinates.y, coordinates.z, zoom, worldId, maptypeId); overviewer.util.setHash(coordinates.x, coordinates.y, coordinates.z, zoom, currWorld, ovconf.name);
}, },
'goToHash': function() { 'goToHash': function() {
// Note: the actual data begins at coords[1], coords[0] is empty. // Note: the actual data begins at coords[1], coords[0] is empty.
@@ -535,52 +695,104 @@ overviewer.util = {
var zoom; var zoom;
var worldid = -1; var world_name = null;
var maptyped = -1; var tileset_name = null;
// The if-statements try to prevent unexpected behaviour when using incomplete hashes, e.g. older links // The if-statements try to prevent unexpected behaviour when using incomplete hashes, e.g. older links
if (coords.length > 4) { if (coords.length > 4) {
zoom = coords[4]; zoom = coords[4];
} }
if (coords.length > 6) { if (coords.length > 6) {
worldid = coords[5]; world_name = decodeURI(coords[5]);
maptypeid = coords[6]; tileset_name = decodeURI(coords[6]);
} }
var worldView = overviewer.collections.worldViews[worldid];
overviewer.mapModel.set({currentWorldView: worldView});
var maptype = worldView.options.mapTypeIds[maptypeid]; var target_layer = overviewer.collections.mapTypes[world_name][tileset_name];
overviewer.map.setMapTypeId(maptype); var ovconf = target_layer.tileSetConfig;
var tsetModel = worldView.model.get("tileSets").at(maptypeid);
var latlngcoords = overviewer.util.fromWorldToLatLng(parseInt(coords[1]), var latlngcoords = overviewer.util.fromWorldToLatLng(parseInt(coords[1]),
parseInt(coords[2]), parseInt(coords[2]),
parseInt(coords[3]), parseInt(coords[3]),
tsetModel); ovconf);
if (zoom == 'max') { if (zoom == 'max') {
zoom = tsetModel.get('maxZoom'); zoom = ovconf.maxZoom;
} else if (zoom == 'min') { } else if (zoom == 'min') {
zoom = tsetModel.get('minZoom'); zoom = ovconf.minZoom;
} else { } else {
zoom = parseInt(zoom); zoom = parseInt(zoom);
if (zoom < 0) { if (zoom < 0) {
// if zoom is negative, treat it as a "zoom out from max" // if zoom is negative, treat it as a "zoom out from max"
zoom += tsetModel.get('maxZoom'); zoom += ovconf.maxZoom;
} else { } else {
// fall back to default zoom // fall back to default zoom
zoom = tsetModel.get('defaultZoom'); zoom = ovconf.defaultZoom;
} }
} }
// clip zoom // clip zoom
if (zoom > tsetModel.get('maxZoom')) if (zoom > ovconf.maxZoom)
zoom = tsetModel.get('maxZoom'); zoom = ovconf.maxZoom;
if (zoom < tsetModel.get('minZoom')) if (zoom < ovconf.minZoom)
zoom = tsetModel.get('minZoom'); zoom = ovconf.minZoom;
overviewer.map.setCenter(latlngcoords); // build a fake event for the world switcher control
overviewer.map.setZoom(zoom); overviewer.worldCtrl.onChange({target: {value: world_name}});
var locationmarker = new overviewer.views.LocationIconView(); overviewer.worldCtrl.select.value = world_name;
locationmarker.render(); if (!overviewer.map.hasLayer(target_layer)) {
overviewer.map.addLayer(target_layer);
}
overviewer.map.setView(latlngcoords, zoom);
if (ovconf.showlocationmarker) {
var locationIcon = L.icon({
iconUrl: overviewerConfig.CONST.image.queryMarker,
iconRetinaUrl: overviewerConfig.CONST.image.queryMarker2x,
iconSize: [32, 37],
iconAnchor: [15, 33],
});
var locationm = L.marker(latlngcoords, { icon: locationIcon,
title: "Linked location"});
overviewer.collections.locationMarker = locationm
overviewer.collections.locationMarker.on('contextmenu', function(ev) {
overviewer.collections.locationMarker.remove();
});
overviewer.collections.locationMarker.on('click', function(ev) {
overviewer.map.setView(ev.latlng);
});
overviewer.collections.locationMarker.addTo(overviewer.map);
}
},
/**
* Generate a function to get the path to a tile at a particular location
* and zoom level.
*
* @param string path
* @param string pathBase
* @param string pathExt
*/
'getTileUrlGenerator': function(path, pathBase, pathExt) {
return function(o) {
var url = path;
var zoom = o.z;
var urlBase = ( pathBase ? pathBase : '' );
if(o.x < 0 || o.x >= Math.pow(2, zoom) ||
o.y < 0 || o.y >= Math.pow(2, zoom)) {
url += '/blank';
} else if(zoom === 0) {
url += '/base';
} else {
for(var z = zoom - 1; z >= 0; --z) {
var x = Math.floor(o.x / Math.pow(2, z)) % 2;
var y = Math.floor(o.y / Math.pow(2, z)) % 2;
url += '/' + (x + 2 * y);
}
}
url = url + '.' + pathExt;
if(typeof overviewerConfig.map.cacheTag !== 'undefined') {
url += '?c=' + overviewerConfig.map.cacheTag;
}
return(urlBase + url);
};
} }
}; };

View File

@@ -1,621 +0,0 @@
overviewer.views= {}
overviewer.views.WorldView = Backbone.View.extend({
initialize: function(opts) {
this.options.mapTypes = [];
this.options.overlayMapTypes = [];
this.options.mapTypeIds = [];
this.options.overlayMapTypeIds = [];
var curTileSet = this.model.get("tileSets").at(0);
var spawn = curTileSet.get("spawn");
if (spawn == "false") {
var spawn = [0,64,0];
}
this.options.lastViewport = [spawn[0],spawn[1],spawn[2],curTileSet.get("defaultZoom")];
this.model.get("tileSets").each(function(tset, index, list) {
// ignore overlays:
var ops = {
getTileUrl: overviewer.gmap.getTileUrlGenerator(tset.get("path"), tset.get("base"), tset.get("imgextension")),
'tileSize': new google.maps.Size(
overviewerConfig.CONST.tileSize,
overviewerConfig.CONST.tileSize),
'maxZoom': tset.get("maxZoom"),
'minZoom': tset.get("minZoom"),
'isPng': (tset.get("imgextension")=="png")
};
var newMapType = new google.maps.ImageMapType(ops);
newMapType.name = tset.get("name");
newMapType.shortname = tset.get("name");
newMapType.alt = "Minecraft " + tset.get("name") + " Map";
newMapType.projection = new overviewer.classes.MapProjection();
newMapType._ov_tileSet = tset;
if (tset.get("isOverlay")) {
newMapType.tiles = tset.get("tilesets");
this.options.overlayMapTypes.push(newMapType);
this.options.overlayMapTypeIds.push(overviewerConfig.CONST.mapDivId + this.model.get("name") + tset.get("name"));
} else {
this.options.mapTypes.push(newMapType);
this.options.mapTypeIds.push(overviewerConfig.CONST.mapDivId + this.model.get("name") + tset.get("name"));
}
}, this);
this.model.get("tileSets").each(function(tset, index, list) {
// ignore non-overlays:
if (!tset.get("isOverlay")) { return; };
});
}
});
overviewer.views.WorldSelectorView = Backbone.View.extend({
initialize: function() {
if(overviewer.collections.worldViews.length > 1) {
$(this.el).addClass("customControl");
// a div will have already been created for us, we just
// need to register it with the google maps control
var selectBox = document.createElement('select');
$.each(overviewer.collections.worldViews, function(index, elem) {
var o = document.createElement("option");
o.value = elem.model.get("name");
o.innerHTML = elem.model.get("name");
if (elem.model == overviewer.mapModel.get("currentWorldView").model) {
o.selected=true;
}
$(o).data("viewObj", elem);
selectBox.appendChild(o);
});
this.el.appendChild(selectBox);
overviewer.map.controls[google.maps.ControlPosition.TOP_LEFT].push(this.el);
}
},
events: {
"change select": "changeWorld"
},
changeWorld: function() {
var selectObj = this.$("select")[0];
var selectedOption = selectObj.options[selectObj.selectedIndex];
overviewer.mapModel.set({currentWorldView: $(selectedOption).data("viewObj")});
//
},
render: function(t) {
//console.log("WorldSelectorView::render() TODO implement this (low priority)");
}
});
overviewer.views.CompassView = Backbone.View.extend({
initialize: function() {
this.el.index=0;
var compassImg = document.createElement('IMG');
compassImg.src = ''; // this will be set properly in the render function (below)
this.el.appendChild(compassImg);
overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(this.el);
},
/**
* CompassView::render
*/
render: function() {
var tsetModel = overviewer.mapView.options.currentTileSet;
var northdir = tsetModel.get("north_direction");
if (northdir == overviewerConfig.CONST.UPPERLEFT)
this.$("IMG").attr("src","compass_upper-left.png");
if (northdir == overviewerConfig.CONST.UPPERRIGHT)
this.$("IMG").attr("src", "compass_upper-right.png");
if (northdir == overviewerConfig.CONST.LOWERLEFT)
this.$("IMG").attr("src", "compass_lower-left.png");
if (northdir == overviewerConfig.CONST.LOWERRIGHT)
this.$("IMG").attr("src", "compass_lower-right.png");
}
});
overviewer.views.CoordboxView = Backbone.View.extend({
initialize: function() {
// Coords box
this.el.id = 'coordsDiv';
this.el.innerHTML = 'coords here';
overviewer.map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(this.el);
},
updateCoords: function(latLng) {
var worldcoords = overviewer.util.fromLatLngToWorld(latLng.lat(),
latLng.lng(),
overviewer.mapView.options.currentTileSet);
var regionfileX = Math.floor(Math.floor(worldcoords.x / 16.0) / 32.0);
var regionfileZ = Math.floor(Math.floor(worldcoords.z / 16.0) / 32.0);
var regionfilename = "r." + regionfileX + "." + regionfileZ + ".mca";
this.el.innerHTML = "<strong>X</strong> " + Math.round(worldcoords.x) +
" <strong>Z</strong> " + Math.round(worldcoords.z) +
" (" + regionfilename + ")";
}
});
overviewer.views.ProgressView = Backbone.View.extend({
initialize: function() {
this.el.id = 'progressDiv';
this.el.innerHTML = 'Current Render Progress';
overviewer.map.controls[google.maps.ControlPosition.BOTTOM_RIGHT].push(this.el);
$(this.el).hide();
$.ajaxSetup({cache: false});
},
updateProgress: function() {
e = this;
$.getJSON('progress.json', null, function(d){
if (!(d == null||d=='')) {
$(e.el).show();
e.el.innerHTML = d['message'];
if (d.update > 0) {
setTimeout("e.updateProgress()", d.update);
} else {
setTimeout("e.updateProgress()", 60000);
e.el.innerHTML="Hidden - d.update < 0";
$(e.el).hide();
}
} else {
e.el.innerHTML="Hidden - !!d==false";
$(e.el).hide();
}
});
}
});
/* GoogleMapView is responsible for dealing with the GoogleMaps API to create the
*/
overviewer.views.GoogleMapView = Backbone.View.extend({
initialize: function(opts) {
this.options.map = null;
var curWorld = this.model.get("currentWorldView").model;
var curTset = curWorld.get("tileSets").at(0);
var spawn = curTset.get("spawn");
if (spawn == "false") {
var spawn = [0,64,0];
}
var mapcenter = overviewer.util.fromWorldToLatLng(
spawn[0],
spawn[1],
spawn[2],
curTset);
this.options.mapTypes=[];
this.options.mapTypeIds=[];
var opts = this.options;
var mapOptions = {};
//
// init the map with some default options. use the first tileset in the first world
this.options.mapOptions = {
zoom: curTset.get("defaultZoom"),
center: mapcenter,
panControl: true,
scaleControl: false,
mapTypeControl: true,
//mapTypeControlOptions: {
//mapTypeIds: this.options.mapTypeIds
//},
mapTypeId: '',
streetViewControl: false,
overviewMapControl: true,
zoomControl: true,
backgroundColor: curTset.get("bgcolor")
};
overviewer.map = new google.maps.Map(this.el, this.options.mapOptions);
// register every ImageMapType with the map
$.each(overviewer.collections.worldViews, function( index, worldView) {
$.each(worldView.options.mapTypes, function(i_index, maptype) {
overviewer.map.mapTypes.set(overviewerConfig.CONST.mapDivId +
worldView.model.get("name") + maptype.shortname , maptype);
});
});
},
/* GoogleMapView::render()
* Should be called when the current world has changed in GoogleMapModel
*/
render: function() {
var view = this.model.get("currentWorldView");
this.options.mapOptions.mapTypeControlOptions = {
mapTypeIds: view.options.mapTypeIds};
this.options.mapOptions.mapTypeId = view.options.mapTypeIds[0];
overviewer.map.setOptions(this.options.mapOptions);
return this;
},
/**
* GoogleMapView::updateCurrentTileset()
* Keeps track of the currently visible tileset
*/
updateCurrentTileset: function() {
var currentWorldView = this.model.get("currentWorldView");
var gmapCurrent = overviewer.map.getMapTypeId();
for (id in currentWorldView.options.mapTypeIds) {
if (currentWorldView.options.mapTypeIds[id] == gmapCurrent) {
this.options.currentTileSet = currentWorldView.options.mapTypes[id]._ov_tileSet;
}
}
// for this world, remember our current viewport (as worldcoords, not LatLng)
//
}
});
/**
* OverlayControlView
*/
overviewer.views.OverlayControlView = Backbone.View.extend({
/** OverlayControlVIew::initialize
*/
initialize: function(opts) {
$(this.el).addClass("customControl");
overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(this.el);
},
registerEvents: function(me) {
overviewer.mapModel.bind("change:currentWorldView", me.render, me);
},
/**
* OverlayControlView::render
*/
render: function() {
this.el.innerHTML="";
// hide all visible overlays:
overviewer.map.overlayMapTypes.clear()
// if this world has no overlays, don't create this control
var mapTypes = overviewer.mapModel.get('currentWorldView').options.overlayMapTypes;
if (mapTypes.length == 0) { return; }
var controlText = document.createElement('DIV');
controlText.innerHTML = "Overlays";
var controlBorder = document.createElement('DIV');
$(controlBorder).addClass('top');
this.el.appendChild(controlBorder);
controlBorder.appendChild(controlText);
var dropdownDiv = document.createElement('DIV');
$(dropdownDiv).addClass('dropDown');
this.el.appendChild(dropdownDiv);
dropdownDiv.innerHTML='';
$(controlText).click(function() {
$(controlBorder).toggleClass('top-active');
$(dropdownDiv).toggle();
});
var currentTileSetPath = overviewer.mapView.options.currentTileSet.get('path');
for (i in mapTypes) {
var mt = mapTypes[i];
// if this overlay specifies a list of valid tilesets, then skip over any invalid tilesets
if ((mt.tiles.length > 0) && (mt.tiles.indexOf(currentTileSetPath) ==-1)) {
continue;
}
this.addItem({label: mt.name,
name: mt.name,
mt: mt,
action: function(this_item, checked) {
if (checked) {
overviewer.map.overlayMapTypes.push(this_item.mt);
} else {
var idx_to_delete = -1;
overviewer.map.overlayMapTypes.forEach(function(e, j) {
if (e == this_item.mt) {
idx_to_delete = j;
}
});
if (idx_to_delete >= 0) {
overviewer.map.overlayMapTypes.removeAt(idx_to_delete);
}
}
}
});
}
},
addItem: function(item) {
var itemDiv = document.createElement('div');
var itemInput = document.createElement('input');
itemInput.type='checkbox';
// if this overlay is already visible, set the checkbox
// to checked
overviewer.map.overlayMapTypes.forEach(function(e, j) {
if (e == item.mt) {
itemInput.checked=true;
}
});
// give it a name
$(itemInput).attr("_mc_overlayname", item.name);
jQuery(itemInput).click((function(local_item) {
return function(e) {
item.action(local_item, e.target.checked);
};
})(item));
this.$(".dropDown")[0].appendChild(itemDiv);
itemDiv.appendChild(itemInput);
var textNode = document.createElement('text');
textNode.innerHTML = item.label + '<br/>';
itemDiv.appendChild(textNode);
}
});
/**
* SignControlView
*/
overviewer.views.SignControlView = Backbone.View.extend({
/** SignControlView::initialize
*/
initialize: function(opts) {
$(this.el).addClass("customControl");
overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(this.el);
},
registerEvents: function(me) {
google.maps.event.addListener(overviewer.map, 'maptypeid_changed', function(event) {
overviewer.mapView.updateCurrentTileset();
// workaround IE issue. bah!
if (typeof markers=="undefined") { return; }
me.render();
// hide markers that are part of other tilesets than this
// for each markerSet, check:
// if the markerSet isnot part of this tileset, hide all of the markers
var curMarkerSet = overviewer.mapView.options.currentTileSet.get("path");
var dataRoot = markers[curMarkerSet];
jQuery.each(markers, function(key, markerSet) {
if (key != curMarkerSet) {
jQuery.each(markerSet, function(i, markerGroup) {
if (typeof markerGroup.markerObjs != "undefined") {
jQuery.each(markerGroup.markerObjs, function(j, markerObj) {
markerObj.setVisible(false);
});
}
});
}
});
return;
});
},
/**
* SignControlView::render
*/
render: function() {
var curMarkerSet = overviewer.mapView.options.currentTileSet.get("path");
//var dataRoot = overviewer.collections.markerInfo[curMarkerSet];
var dataRoot = markers[curMarkerSet];
this.el.innerHTML="";
// if we have no markerSets for this tileset, do nothing:
if (!dataRoot) { return; }
var controlText = document.createElement('DIV');
controlText.innerHTML = overviewer.mapView.options.currentTileSet.get("poititle");
var controlBorder = document.createElement('DIV');
$(controlBorder).addClass('top');
this.el.appendChild(controlBorder);
controlBorder.appendChild(controlText);
var dropdownDiv = document.createElement('DIV');
$(dropdownDiv).addClass('dropDown');
this.el.appendChild(dropdownDiv);
dropdownDiv.innerHTML='';
// add the functionality to toggle visibility of the items
$(controlText).click(function() {
$(controlBorder).toggleClass('top-active');
$(dropdownDiv).toggle();
});
//dataRoot['markers'] = [];
//
for (i in dataRoot) {
var groupName = dataRoot[i].groupName;
if (!dataRoot[i].created) {
dataRoot[i].markerObjs = [];
for (j in markersDB[groupName].raw) {
var entity = markersDB[groupName].raw[j];
if (entity['icon']) {
iconURL = entity['icon'];
} else {
iconURL = dataRoot[i].icon;
}
var marker = new google.maps.Marker({
'position': overviewer.util.fromWorldToLatLng(entity.x,
entity.y, entity.z, overviewer.mapView.options.currentTileSet),
'map': overviewer.map,
'title': jQuery.trim(entity.hovertext),
'content': jQuery.trim(entity.text),
'icon': iconURL,
'visible': false
});
if(entity['createInfoWindow'] == true) {
overviewer.util.createMarkerInfoWindow(marker);
} else {
if(dataRoot[i].createInfoWindow == true) {
overviewer.util.createMarkerInfoWindow(marker);
}
}
dataRoot[i].markerObjs.push(marker);
// Polyline stuff added by FreakusGeekus. Probably needs work.
if (typeof entity['polyline'] != 'undefined') {
var polypath = new Array();
for (point in entity.polyline) {
polypath.push(overviewer.util.fromWorldToLatLng(entity.polyline[point].x, entity.polyline[point].y, entity.polyline[point].z, overviewer.mapView.options.currentTileSet));
}
var polyline = new google.maps.Polyline({
'path': polypath,
'clickable': false,
'map': overviewer.map,
'visible': false,
'strokeColor': entity['strokeColor']
});
dataRoot[i].markerObjs.push(polyline);
}
// Polygons
if (typeof entity['polygon'] != 'undefined') {
var polypath = new Array();
for (point in entity.polygon) {
polypath.push(overviewer.util.fromWorldToLatLng(entity.polygon[point].x, entity.polygon[point].y, entity.polygon[point].z, overviewer.mapView.options.currentTileSet));
}
var polygon = new google.maps.Polygon({
'clickable': false,
'fillColor': entity['fillColor'],
'fillOpacity': entity['fillOpacity'],
'map': overviewer.map,
'path': polypath,
'strokeColor': entity['strokeColor'],
'strokeOpacity': entity['strokeOpacity'],
'visible': false
});
dataRoot[i].markerObjs.push(polygon);
}
}
dataRoot[i].created = true;
}
}
// add some menus
for (i in dataRoot) {
var group = dataRoot[i];
this.addItem({group: group, action:function(this_item, checked) {
this_item.group.checked = checked;
jQuery.each(this_item.group.markerObjs, function(i, markerObj) {
markerObj.setVisible(checked);
});
}});
if (group.checked) {
jQuery.each(group.markerObjs, function(i, markerObj) {
markerObj.setVisible(true);
});
}
}
},
addItem: function(item) {
var itemDiv = document.createElement('div');
var itemInput = document.createElement('input');
itemInput.type='checkbox';
if (item.group.checked) {
itemInput.checked="true";
}
// give it a name
$(itemInput).data('label',item.group.displayName);
$(itemInput).attr("_mc_groupname", item.group.gropuName);
jQuery(itemInput).click((function(local_item) {
return function(e) {
item.action(local_item, e.target.checked);
};
})(item));
this.$(".dropDown")[0].appendChild(itemDiv);
itemDiv.appendChild(itemInput);
var textNode = document.createElement('text');
if(item.icon) {
textNode.innerHTML = '<img width="15" height="15" src="' +
item.icon + '">' + item.group.displayName + '&nbsp;<br/>';
} else {
textNode.innerHTML = item.group.displayName + '&nbsp;<br/>';
}
itemDiv.appendChild(textNode);
itemDiv.style.whiteSpace = "nowrap";
}
});
/**
* SpawnIconView
*/
overviewer.views.SpawnIconView = Backbone.View.extend({
render: function() {
//
var curTileSet = overviewer.mapView.options.currentTileSet;
if (overviewer.collections.spawnMarker) {
overviewer.collections.spawnMarker.setMap(null);
overviewer.collections.spawnMarker = null;
}
var spawn = curTileSet.get("spawn");
if (spawn) {
overviewer.collections.spawnMarker = new google.maps.Marker({
'position': overviewer.util.fromWorldToLatLng(spawn[0],
spawn[1], spawn[2], overviewer.mapView.options.currentTileSet),
'map': overviewer.map,
'title': 'spawn',
'icon': overviewerConfig.CONST.image.spawnMarker,
'visible': false
});
overviewer.collections.spawnMarker.setVisible(true);
}
}
});
overviewer.views.LocationIconView = Backbone.View.extend({
render: function() {
//
if (overviewer.collections.locationMarker) {
overviewer.collections.locationMarker.setMap(null);
overviewer.collections.locationMarker = null;
}
overviewer.collections.locationMarker = new google.maps.Marker({
'position': overviewer.map.getCenter(),
'map': overviewer.map,
'title': 'location',
'icon': overviewerConfig.CONST.image.queryMarker,
'visible': false
});
overviewer.collections.locationMarker.setVisible(overviewer.mapView.options.currentTileSet.get("showlocationmarker"));
}
});

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg2"
height="37"
width="32"
version="1.1">
<defs
id="defs4">
<filter
id="filter3802"
height="1.5"
width="1.5"
y="-0.25"
x="-0.25">
<feGaussianBlur
id="feGaussianBlur3804"
in="SourceAlpha"
stdDeviation="2"
result="blur" />
<feColorMatrix
id="feColorMatrix3806"
result="bluralpha"
type="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.5 0 " />
<feOffset
id="feOffset3808"
in="bluralpha"
dx="0"
dy="0"
result="offsetBlur" />
<feMerge
id="feMerge3810">
<feMergeNode
id="feMergeNode3812"
in="offsetBlur" />
<feMergeNode
id="feMergeNode3814"
in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
transform="translate(0,-1015.3622)">
<path
style="fill:#fcf357;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;filter:url(#filter3802)"
id="rect2987"
d="m 5.3953731,1018.8294 c -1.0681842,0 -1.928114,0.8381 -1.928114,1.8793 l 0,20.6699 c 0,1.0409 0.8599298,1.879 1.928114,1.879 l 4.3382568,0 5.7843411,5.6373 0.964058,0 5.784341,-5.6373 4.338257,0 c 1.068185,0 1.928114,-0.8381 1.928114,-1.879 l 0,-20.6699 c 0,-1.0412 -0.859929,-1.8793 -1.928114,-1.8793 l -21.2092539,0 z" />
</g>
<path
id="path4304"
d="m 16,8 -9,7 0,2 2,0 0,6 5,0 0,-5 4,0 0,5 5,0 0,-6 2,0 0,-2 z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
id="path4308"
d="m 19,13 0,-4 3,0 0,6"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
id="svg2"
height="37"
width="32"
version="1.1">
<defs
id="defs4">
<filter
id="filter3802"
height="1.5"
width="1.5"
y="-0.25"
x="-0.25">
<feGaussianBlur
id="feGaussianBlur3804"
in="SourceAlpha"
stdDeviation="2"
result="blur" />
<feColorMatrix
id="feColorMatrix3806"
result="bluralpha"
type="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.5 0 " />
<feOffset
id="feOffset3808"
in="bluralpha"
dx="0"
dy="0"
result="offsetBlur" />
<feMerge
id="feMerge3810">
<feMergeNode
id="feMergeNode3812"
in="offsetBlur" />
<feMergeNode
id="feMergeNode3814"
in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
transform="translate(0,-1015.3622)">
<path
style="fill:#d38d5f;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;filter:url(#filter3802)"
id="rect2987"
d="m 5.3953731,1018.8294 c -1.0681842,0 -1.928114,0.8381 -1.928114,1.8793 l 0,20.6699 c 0,1.0409 0.8599298,1.879 1.928114,1.879 l 4.3382568,0 5.7843411,5.6373 0.964058,0 5.784341,-5.6373 4.338257,0 c 1.068185,0 1.928114,-0.8381 1.928114,-1.879 l 0,-20.6699 c 0,-1.0412 -0.859929,-1.8793 -1.928114,-1.8793 l -21.2092539,0 z" />
</g>
<circle
r="2.5"
cy="24"
cx="16"
id="path4171"
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:65;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
id="path4173"
d="m 16,20 -6,-6 4,0 0,-8 4,0 0,8 4,0 z"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 826 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

View File

@@ -8,17 +8,16 @@
<meta name="generator" content="Minecraft-Overviewer {version}" /> <meta name="generator" content="Minecraft-Overviewer {version}" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" /> <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<link rel="stylesheet" href="overviewer.css" type="text/css" /> <script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script type="text/javascript" src="https://maps.google.com/maps/api/js"></script>
<script type="text/javascript" src="underscore.js"></script>
<script type="text/javascript" src="backbone.js"></script>
<script type="text/javascript" src="overviewerConfig.js"></script> <script type="text/javascript" src="overviewerConfig.js"></script>
<script type="text/javascript" src="overviewer.js"></script> <script type="text/javascript" src="overviewer.js"></script>
<script type="text/javascript" src="baseMarkers.js"></script> <script type="text/javascript" src="baseMarkers.js"></script>
<link rel="stylesheet" href="leaflet.css" />
<script src="leaflet.js"></script>
<link rel="stylesheet" href="overviewer.css" type="text/css" />
</head> </head>
<!-- Generated at: {time} --> <!-- Generated at: {time} -->

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,636 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg,
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer {
max-width: none !important;
max-height: none !important;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-tile {
will-change: opacity;
}
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
-o-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
-o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
-o-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline: 0;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-container a.leaflet-active {
outline: 2px solid orange;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a,
.leaflet-bar a:hover {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path {
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.7);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover {
text-decoration: underline;
}
.leaflet-container .leaflet-control-attribution,
.leaflet-container .leaflet-control-scale {
font-size: 11px;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
font-size: 11px;
white-space: nowrap;
overflow: hidden;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: #fff;
background: rgba(255, 255, 255, 0.5);
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 19px;
line-height: 1.4;
}
.leaflet-popup-content p {
margin: 18px 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
padding: 4px 4px 0 0;
border: none;
text-align: center;
width: 18px;
height: 14px;
font: 16px/14px Tahoma, Verdana, sans-serif;
color: #c3c3c3;
text-decoration: none;
font-weight: bold;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover {
color: #999;
}
.leaflet-popup-scrolled {
overflow: auto;
border-bottom: 1px solid #ddd;
border-top: 1px solid #ddd;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-popup-tip-container {
margin-top: -1px;
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-clickable {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}

File diff suppressed because one or more lines are too long

View File

@@ -150,3 +150,26 @@ div.searchResultItem img {
width: 24px; width: 24px;
height: 24px; height: 24px;
} }
div.worldcontrol {
border: 2px solid rgba(0,0,0,0.2);
border-radius: 5px;
background: #fff;
padding: 2px;
}
div.worldcontrol select {
background: #fff;
border: 1px solid #fff;
color: #333;
font-size: 1.2em;
}
.leaflet-container .coordbox {
box-shadow: none;
font-size: 11px;
background: rgba(255, 255, 255, 0.7);
margin: 0;
padding: 0 5px;
color: #333;
}

View File

@@ -1,990 +0,0 @@
// Underscore.js 1.3.0
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele's Functional, and John Resig's Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function() {
// Baseline setup
// --------------
// Establish the root object, `window` in the browser, or `global` on the server.
var root = this;
// Save the previous value of the `_` variable.
var previousUnderscore = root._;
// Establish the object that gets returned to break out of a loop iteration.
var breaker = {};
// Save bytes in the minified (but not gzipped) version:
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
// Create quick reference variables for speed access to core prototypes.
var slice = ArrayProto.slice,
unshift = ArrayProto.unshift,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
// All **ECMAScript 5** native function implementations that we hope to use
// are declared here.
var
nativeForEach = ArrayProto.forEach,
nativeMap = ArrayProto.map,
nativeReduce = ArrayProto.reduce,
nativeReduceRight = ArrayProto.reduceRight,
nativeFilter = ArrayProto.filter,
nativeEvery = ArrayProto.every,
nativeSome = ArrayProto.some,
nativeIndexOf = ArrayProto.indexOf,
nativeLastIndexOf = ArrayProto.lastIndexOf,
nativeIsArray = Array.isArray,
nativeKeys = Object.keys,
nativeBind = FuncProto.bind;
// Create a safe reference to the Underscore object for use below.
var _ = function(obj) { return new wrapper(obj); };
// Export the Underscore object for **Node.js**, with
// backwards-compatibility for the old `require()` API. If we're in
// the browser, add `_` as a global object via a string identifier,
// for Closure Compiler "advanced" mode.
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
root['_'] = _;
}
// Current version.
_.VERSION = '1.3.0';
// Collection Functions
// --------------------
// The cornerstone, an `each` implementation, aka `forEach`.
// Handles objects with the built-in `forEach`, arrays, and raw objects.
// Delegates to **ECMAScript 5**'s native `forEach` if available.
var each = _.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
for (var key in obj) {
if (hasOwnProperty.call(obj, key)) {
if (iterator.call(context, obj[key], key, obj) === breaker) return;
}
}
}
};
// Return the results of applying the iterator to each element.
// Delegates to **ECMAScript 5**'s native `map` if available.
_.map = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
each(obj, function(value, index, list) {
results[results.length] = iterator.call(context, value, index, list);
});
if (obj.length === +obj.length) results.length = obj.length;
return results;
};
// **Reduce** builds up a single result from a list of values, aka `inject`,
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduce && obj.reduce === nativeReduce) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
each(obj, function(value, index, list) {
if (!initial) {
memo = value;
initial = true;
} else {
memo = iterator.call(context, memo, value, index, list);
}
});
if (!initial) throw new TypeError('Reduce of empty array with no initial value');
return memo;
};
// The right-associative version of reduce, also known as `foldr`.
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if (obj == null) obj = [];
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if (context) iterator = _.bind(iterator, context);
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
var reversed = _.toArray(obj).reverse();
if (context && !initial) iterator = _.bind(iterator, context);
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
};
// Return the first value which passes a truth test. Aliased as `detect`.
_.find = _.detect = function(obj, iterator, context) {
var result;
any(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) {
result = value;
return true;
}
});
return result;
};
// Return all the elements that pass a truth test.
// Delegates to **ECMAScript 5**'s native `filter` if available.
// Aliased as `select`.
_.filter = _.select = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
each(obj, function(value, index, list) {
if (iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Return all the elements for which a truth test fails.
_.reject = function(obj, iterator, context) {
var results = [];
if (obj == null) return results;
each(obj, function(value, index, list) {
if (!iterator.call(context, value, index, list)) results[results.length] = value;
});
return results;
};
// Determine whether all of the elements match a truth test.
// Delegates to **ECMAScript 5**'s native `every` if available.
// Aliased as `all`.
_.every = _.all = function(obj, iterator, context) {
var result = true;
if (obj == null) return result;
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
each(obj, function(value, index, list) {
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
});
return result;
};
// Determine if at least one element in the object matches a truth test.
// Delegates to **ECMAScript 5**'s native `some` if available.
// Aliased as `any`.
var any = _.some = _.any = function(obj, iterator, context) {
iterator || (iterator = _.identity);
var result = false;
if (obj == null) return result;
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
each(obj, function(value, index, list) {
if (result || (result = iterator.call(context, value, index, list))) return breaker;
});
return !!result;
};
// Determine if a given value is included in the array or object using `===`.
// Aliased as `contains`.
_.include = _.contains = function(obj, target) {
var found = false;
if (obj == null) return found;
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
found = any(obj, function(value) {
return value === target;
});
return found;
};
// Invoke a method (with arguments) on every item in a collection.
_.invoke = function(obj, method) {
var args = slice.call(arguments, 2);
return _.map(obj, function(value) {
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
});
};
// Convenience version of a common use case of `map`: fetching a property.
_.pluck = function(obj, key) {
return _.map(obj, function(value){ return value[key]; });
};
// Return the maximum element or (element-based computation).
_.max = function(obj, iterator, context) {
if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return -Infinity;
var result = {computed : -Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed >= result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Return the minimum element (or element-based computation).
_.min = function(obj, iterator, context) {
if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
if (!iterator && _.isEmpty(obj)) return Infinity;
var result = {computed : Infinity};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed < result.computed && (result = {value : value, computed : computed});
});
return result.value;
};
// Shuffle an array.
_.shuffle = function(obj) {
var shuffled = [], rand;
each(obj, function(value, index, list) {
if (index == 0) {
shuffled[0] = value;
} else {
rand = Math.floor(Math.random() * (index + 1));
shuffled[index] = shuffled[rand];
shuffled[rand] = value;
}
});
return shuffled;
};
// Sort the object's values by a criterion produced by an iterator.
_.sortBy = function(obj, iterator, context) {
return _.pluck(_.map(obj, function(value, index, list) {
return {
value : value,
criteria : iterator.call(context, value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
return a < b ? -1 : a > b ? 1 : 0;
}), 'value');
};
// Groups the object's values by a criterion. Pass either a string attribute
// to group by, or a function that returns the criterion.
_.groupBy = function(obj, val) {
var result = {};
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
each(obj, function(value, index) {
var key = iterator(value, index);
(result[key] || (result[key] = [])).push(value);
});
return result;
};
// Use a comparator function to figure out at what index an object should
// be inserted so as to maintain order. Uses binary search.
_.sortedIndex = function(array, obj, iterator) {
iterator || (iterator = _.identity);
var low = 0, high = array.length;
while (low < high) {
var mid = (low + high) >> 1;
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
}
return low;
};
// Safely convert anything iterable into a real, live array.
_.toArray = function(iterable) {
if (!iterable) return [];
if (iterable.toArray) return iterable.toArray();
if (_.isArray(iterable)) return slice.call(iterable);
if (_.isArguments(iterable)) return slice.call(iterable);
return _.values(iterable);
};
// Return the number of elements in an object.
_.size = function(obj) {
return _.toArray(obj).length;
};
// Array Functions
// ---------------
// Get the first element of an array. Passing **n** will return the first N
// values in the array. Aliased as `head`. The **guard** check allows it to work
// with `_.map`.
_.first = _.head = function(array, n, guard) {
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};
// Returns everything but the last entry of the array. Especcialy useful on
// the arguments object. Passing **n** will return all the values in
// the array, excluding the last N. The **guard** check allows it to work with
// `_.map`.
_.initial = function(array, n, guard) {
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
};
// Get the last element of an array. Passing **n** will return the last N
// values in the array. The **guard** check allows it to work with `_.map`.
_.last = function(array, n, guard) {
if ((n != null) && !guard) {
return slice.call(array, Math.max(array.length - n, 0));
} else {
return array[array.length - 1];
}
};
// Returns everything but the first entry of the array. Aliased as `tail`.
// Especially useful on the arguments object. Passing an **index** will return
// the rest of the values in the array from that index onward. The **guard**
// check allows it to work with `_.map`.
_.rest = _.tail = function(array, index, guard) {
return slice.call(array, (index == null) || guard ? 1 : index);
};
// Trim out all falsy values from an array.
_.compact = function(array) {
return _.filter(array, function(value){ return !!value; });
};
// Return a completely flattened version of an array.
_.flatten = function(array, shallow) {
return _.reduce(array, function(memo, value) {
if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
memo[memo.length] = value;
return memo;
}, []);
};
// Return a version of the array that does not contain the specified value(s).
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
// Produce a duplicate-free version of the array. If the array has already
// been sorted, you have the option of using a faster algorithm.
// Aliased as `unique`.
_.uniq = _.unique = function(array, isSorted, iterator) {
var initial = iterator ? _.map(array, iterator) : array;
var result = [];
_.reduce(initial, function(memo, el, i) {
if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) {
memo[memo.length] = el;
result[result.length] = array[i];
}
return memo;
}, []);
return result;
};
// Produce an array that contains the union: each distinct element from all of
// the passed-in arrays.
_.union = function() {
return _.uniq(_.flatten(arguments, true));
};
// Produce an array that contains every item shared between all the
// passed-in arrays. (Aliased as "intersect" for back-compat.)
_.intersection = _.intersect = function(array) {
var rest = slice.call(arguments, 1);
return _.filter(_.uniq(array), function(item) {
return _.every(rest, function(other) {
return _.indexOf(other, item) >= 0;
});
});
};
// Take the difference between one array and a number of other arrays.
// Only the elements present in just the first array will remain.
_.difference = function(array) {
var rest = _.flatten(slice.call(arguments, 1));
return _.filter(array, function(value){ return !_.include(rest, value); });
};
// Zip together multiple lists into a single array -- elements that share
// an index go together.
_.zip = function() {
var args = slice.call(arguments);
var length = _.max(_.pluck(args, 'length'));
var results = new Array(length);
for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
return results;
};
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
// we need this function. Return the position of the first occurrence of an
// item in an array, or -1 if the item is not included in the array.
// Delegates to **ECMAScript 5**'s native `indexOf` if available.
// If the array is large and already in sort order, pass `true`
// for **isSorted** to use binary search.
_.indexOf = function(array, item, isSorted) {
if (array == null) return -1;
var i, l;
if (isSorted) {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
return -1;
};
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
_.lastIndexOf = function(array, item) {
if (array == null) return -1;
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
var i = array.length;
while (i--) if (i in array && array[i] === item) return i;
return -1;
};
// Generate an integer Array containing an arithmetic progression. A port of
// the native Python `range()` function. See
// [the Python documentation](http://docs.python.org/library/functions.html#range).
_.range = function(start, stop, step) {
if (arguments.length <= 1) {
stop = start || 0;
start = 0;
}
step = arguments[2] || 1;
var len = Math.max(Math.ceil((stop - start) / step), 0);
var idx = 0;
var range = new Array(len);
while(idx < len) {
range[idx++] = start;
start += step;
}
return range;
};
// Function (ahem) Functions
// ------------------
// Reusable constructor function for prototype setting.
var ctor = function(){};
// Create a function bound to a given object (assigning `this`, and arguments,
// optionally). Binding with arguments is also known as `curry`.
// Delegates to **ECMAScript 5**'s native `Function.bind` if available.
// We check for `func.bind` first, to fail fast when `func` is undefined.
_.bind = function bind(func, context) {
var bound, args;
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func)) throw new TypeError;
args = slice.call(arguments, 2);
return bound = function() {
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
ctor.prototype = func.prototype;
var self = new ctor;
var result = func.apply(self, args.concat(slice.call(arguments)));
if (Object(result) === result) return result;
return self;
};
};
// Bind all of an object's methods to that object. Useful for ensuring that
// all callbacks defined on an object belong to it.
_.bindAll = function(obj) {
var funcs = slice.call(arguments, 1);
if (funcs.length == 0) funcs = _.functions(obj);
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
return obj;
};
// Memoize an expensive function by storing its results.
_.memoize = function(func, hasher) {
var memo = {};
hasher || (hasher = _.identity);
return function() {
var key = hasher.apply(this, arguments);
return hasOwnProperty.call(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
// Delays a function for the given number of milliseconds, and then calls
// it with the arguments supplied.
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
return setTimeout(function(){ return func.apply(func, args); }, wait);
};
// Defers a function, scheduling it to run after the current call stack has
// cleared.
_.defer = function(func) {
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
};
// Returns a function, that, when invoked, will only be triggered at most once
// during a given window of time.
_.throttle = function(func, wait) {
var context, args, timeout, throttling, more;
var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
return function() {
context = this; args = arguments;
var later = function() {
timeout = null;
if (more) func.apply(context, args);
whenDone();
};
if (!timeout) timeout = setTimeout(later, wait);
if (throttling) {
more = true;
} else {
func.apply(context, args);
}
whenDone();
throttling = true;
};
};
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds.
_.debounce = function(func, wait) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
func.apply(context, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
// Returns a function that will be executed at most one time, no matter how
// often you call it. Useful for lazy initialization.
_.once = function(func) {
var ran = false, memo;
return function() {
if (ran) return memo;
ran = true;
return memo = func.apply(this, arguments);
};
};
// Returns the first function passed as an argument to the second,
// allowing you to adjust arguments, run code before and after, and
// conditionally execute the original function.
_.wrap = function(func, wrapper) {
return function() {
var args = [func].concat(slice.call(arguments, 0));
return wrapper.apply(this, args);
};
};
// Returns a function that is the composition of a list of functions, each
// consuming the return value of the function that follows.
_.compose = function() {
var funcs = arguments;
return function() {
var args = arguments;
for (var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
return args[0];
};
};
// Returns a function that will only be executed after being called N times.
_.after = function(times, func) {
if (times <= 0) return func();
return function() {
if (--times < 1) { return func.apply(this, arguments); }
};
};
// Object Functions
// ----------------
// Retrieve the names of an object's properties.
// Delegates to **ECMAScript 5**'s native `Object.keys`
_.keys = nativeKeys || function(obj) {
if (obj !== Object(obj)) throw new TypeError('Invalid object');
var keys = [];
for (var key in obj) if (hasOwnProperty.call(obj, key)) keys[keys.length] = key;
return keys;
};
// Retrieve the values of an object's properties.
_.values = function(obj) {
return _.map(obj, _.identity);
};
// Return a sorted list of the function names available on the object.
// Aliased as `methods`
_.functions = _.methods = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
};
// Extend a given object with all the properties in passed-in object(s).
_.extend = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (source[prop] !== void 0) obj[prop] = source[prop];
}
});
return obj;
};
// Fill in a given object with default properties.
_.defaults = function(obj) {
each(slice.call(arguments, 1), function(source) {
for (var prop in source) {
if (obj[prop] == null) obj[prop] = source[prop];
}
});
return obj;
};
// Create a (shallow-cloned) duplicate of an object.
_.clone = function(obj) {
if (!_.isObject(obj)) return obj;
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
// Invokes interceptor with the obj, and then returns obj.
// The primary purpose of this method is to "tap into" a method chain, in
// order to perform operations on intermediate results within the chain.
_.tap = function(obj, interceptor) {
interceptor(obj);
return obj;
};
// Internal recursive comparison function.
function eq(a, b, stack) {
// Identical objects are equal. `0 === -0`, but they aren't identical.
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
if (a === b) return a !== 0 || 1 / a == 1 / b;
// A strict comparison is necessary because `null == undefined`.
if (a == null || b == null) return a === b;
// Unwrap any wrapped objects.
if (a._chain) a = a._wrapped;
if (b._chain) b = b._wrapped;
// Invoke a custom `isEqual` method if one is provided.
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
// Compare `[[Class]]` names.
var className = toString.call(a);
if (className != toString.call(b)) return false;
switch (className) {
// Strings, numbers, dates, and booleans are compared by value.
case '[object String]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
return a == String(b);
case '[object Number]':
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
// other numeric values.
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
case '[object Date]':
case '[object Boolean]':
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
// millisecond representations. Note that invalid dates with millisecond representations
// of `NaN` are not equivalent.
return +a == +b;
// RegExps are compared by their source patterns and flags.
case '[object RegExp]':
return a.source == b.source &&
a.global == b.global &&
a.multiline == b.multiline &&
a.ignoreCase == b.ignoreCase;
}
if (typeof a != 'object' || typeof b != 'object') return false;
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
var length = stack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if (stack[length] == a) return true;
}
// Add the first object to the stack of traversed objects.
stack.push(a);
var size = 0, result = true;
// Recursively compare objects and arrays.
if (className == '[object Array]') {
// Compare array lengths to determine if a deep comparison is necessary.
size = a.length;
result = size == b.length;
if (result) {
// Deep compare the contents, ignoring non-numeric properties.
while (size--) {
// Ensure commutative equality for sparse arrays.
if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
}
}
} else {
// Objects with different constructors are not equivalent.
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
// Deep compare objects.
for (var key in a) {
if (hasOwnProperty.call(a, key)) {
// Count the expected number of properties.
size++;
// Deep compare each member.
if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break;
}
}
// Ensure that both objects contain the same number of properties.
if (result) {
for (key in b) {
if (hasOwnProperty.call(b, key) && !(size--)) break;
}
result = !size;
}
}
// Remove the first object from the stack of traversed objects.
stack.pop();
return result;
}
// Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) {
return eq(a, b, []);
};
// Is a given array, string, or object empty?
// An "empty" object has no enumerable own-properties.
_.isEmpty = function(obj) {
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
return true;
};
// Is a given value a DOM element?
_.isElement = function(obj) {
return !!(obj && obj.nodeType == 1);
};
// Is a given value an array?
// Delegates to ECMA5's native Array.isArray
_.isArray = nativeIsArray || function(obj) {
return toString.call(obj) == '[object Array]';
};
// Is a given variable an object?
_.isObject = function(obj) {
return obj === Object(obj);
};
// Is a given variable an arguments object?
_.isArguments = function(obj) {
return toString.call(obj) == '[object Arguments]';
};
if (!_.isArguments(arguments)) {
_.isArguments = function(obj) {
return !!(obj && hasOwnProperty.call(obj, 'callee'));
};
}
// Is a given value a function?
_.isFunction = function(obj) {
return toString.call(obj) == '[object Function]';
};
// Is a given value a string?
_.isString = function(obj) {
return toString.call(obj) == '[object String]';
};
// Is a given value a number?
_.isNumber = function(obj) {
return toString.call(obj) == '[object Number]';
};
// Is the given value `NaN`?
_.isNaN = function(obj) {
// `NaN` is the only value for which `===` is not reflexive.
return obj !== obj;
};
// Is a given value a boolean?
_.isBoolean = function(obj) {
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
};
// Is a given value a date?
_.isDate = function(obj) {
return toString.call(obj) == '[object Date]';
};
// Is the given value a regular expression?
_.isRegExp = function(obj) {
return toString.call(obj) == '[object RegExp]';
};
// Is a given value equal to null?
_.isNull = function(obj) {
return obj === null;
};
// Is a given variable undefined?
_.isUndefined = function(obj) {
return obj === void 0;
};
// Utility Functions
// -----------------
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
// previous owner. Returns a reference to the Underscore object.
_.noConflict = function() {
root._ = previousUnderscore;
return this;
};
// Keep the identity function around for default iterators.
_.identity = function(value) {
return value;
};
// Run a function **n** times.
_.times = function (n, iterator, context) {
for (var i = 0; i < n; i++) iterator.call(context, i);
};
// Escape a string for HTML interpolation.
_.escape = function(string) {
return (''+string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
};
// Add your own custom functions to the Underscore object, ensuring that
// they're correctly added to the OOP wrapper as well.
_.mixin = function(obj) {
each(_.functions(obj), function(name){
addToWrapper(name, _[name] = obj[name]);
});
};
// Generate a unique integer id (unique within the entire client session).
// Useful for temporary DOM ids.
var idCounter = 0;
_.uniqueId = function(prefix) {
var id = idCounter++;
return prefix ? prefix + id : id;
};
// By default, Underscore uses ERB-style template delimiters, change the
// following template settings to use alternative delimiters.
_.templateSettings = {
evaluate : /<%([\s\S]+?)%>/g,
interpolate : /<%=([\s\S]+?)%>/g,
escape : /<%-([\s\S]+?)%>/g
};
// When customizing `templateSettings`, if you don't want to define an
// interpolation, evaluation or escaping regex, we need one that is
// guaranteed not to match.
var noMatch = /.^/;
// JavaScript micro-templating, similar to John Resig's implementation.
// Underscore templating handles arbitrary delimiters, preserves whitespace,
// and correctly escapes quotes within interpolated code.
_.template = function(str, data) {
var c = _.templateSettings;
var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
'with(obj||{}){__p.push(\'' +
str.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(c.escape || noMatch, function(match, code) {
return "',_.escape(" + code.replace(/\\'/g, "'") + "),'";
})
.replace(c.interpolate || noMatch, function(match, code) {
return "'," + code.replace(/\\'/g, "'") + ",'";
})
.replace(c.evaluate || noMatch, function(match, code) {
return "');" + code.replace(/\\'/g, "'")
.replace(/[\r\n\t]/g, ' ')
.replace(/\\\\/g, '\\') + ";__p.push('";
})
.replace(/\r/g, '\\r')
.replace(/\n/g, '\\n')
.replace(/\t/g, '\\t')
+ "');}return __p.join('');";
var func = new Function('obj', '_', tmpl);
if (data) return func(data, _);
return function(data) {
return func.call(this, data, _);
};
};
// Add a "chain" function, which will delegate to the wrapper.
_.chain = function(obj) {
return _(obj).chain();
};
// The OOP Wrapper
// ---------------
// If Underscore is called as a function, it returns a wrapped object that
// can be used OO-style. This wrapper holds altered versions of all the
// underscore functions. Wrapped objects may be chained.
var wrapper = function(obj) { this._wrapped = obj; };
// Expose `wrapper.prototype` as `_.prototype`
_.prototype = wrapper.prototype;
// Helper function to continue chaining intermediate results.
var result = function(obj, chain) {
return chain ? _(obj).chain() : obj;
};
// A method to easily add functions to the OOP wrapper.
var addToWrapper = function(name, func) {
wrapper.prototype[name] = function() {
var args = slice.call(arguments);
unshift.call(args, this._wrapped);
return result(func.apply(_, args), this._chain);
};
};
// Add all of the Underscore functions to the wrapper object.
_.mixin(_);
// Add all mutator Array functions to the wrapper.
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
var wrapped = this._wrapped;
method.apply(wrapped, arguments);
var length = wrapped.length;
if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
return result(wrapped, this._chain);
};
});
// Add all accessor Array functions to the wrapper.
each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
return result(method.apply(this._wrapped, arguments), this._chain);
};
});
// Start chaining a wrapped Underscore object.
wrapper.prototype.chain = function() {
this._chain = true;
return this;
};
// Extracts the result from a wrapped and chained object.
wrapper.prototype.value = function() {
return this._wrapped;
};
}).call(this);