var overviewer = { /** * This holds the map, probably the most important var in this file */ 'map': null, /** * These are collections of data used in various places */ 'collections': { /** * A list of lists of raw marker data objects, this will allow for an * arbitrary number of marker data sources. This replaces the old * markerData var from markers.js. Now you can add markers by including * a file with: * overviewer.collections.markerDatas.push([]); */ 'markerDatas': [], /** * The actual Marker objects are stored here. */ 'markers': {}, /** * Same as markerDatas, list of lists of raw region objects. */ 'regionDatas': [], /** * The actual Region objects. */ 'regions': {}, /** * Overlay mapTypes (like Spawn) will go in here. */ 'overlays': [], /** * MapTypes that aren't overlays will end up in here. */ 'mapTypes': {}, /** * The mapType names are in here. */ 'mapTypeIds': [], /** * This is the current infoWindow object, we keep track of it so that * there is only one open at a time. */ 'infoWindow': null }, 'util': { /** * General initialization function, called when the page is loaded. * Probably shouldn't need changing unless some very different kind of new * feature gets added. */ 'initialize': function() { overviewer.util.initializeClassPrototypes(); overviewer.util.initializeMapTypes(); overviewer.util.initializeMap(); overviewer.util.initializeMarkers(); overviewer.util.initializeRegions(); overviewer.util.createMapControls(); overviewer.util.createSearchBox(); }, /** * This adds some methods to these classes because Javascript is stupid * and this seems like the best way to avoid re-creating the same methods * on each object at object creation time. */ 'initializeClassPrototypes': function() { overviewer.classes.MapProjection.prototype.fromLatLngToPoint = function(latLng) { var x = latLng.lng() * overviewerConfig.CONST.tileSize; var y = latLng.lat() * overviewerConfig.CONST.tileSize; return new google.maps.Point(x, y); }; overviewer.classes.MapProjection.prototype.fromPointToLatLng = function(point) { var lng = point.x * this.inverseTileSize; var lat = point.y * this.inverseTileSize; return new google.maps.LatLng(lat, lng); }; overviewer.classes.CoordMapType.prototype.getTile = function(coord, zoom, ownerDocument) { var div = ownerDocument.createElement('DIV'); div.innerHTML = '(' + coord.x + ', ' + coord.y + ', ' + zoom + ')' + '
'; //TODO: figure out how to get the current mapType, I think this //will add the maptile url to the grid thing once it works //div.innerHTML += overviewer.collections.mapTypes[0].getTileUrl(coord, zoom); //this should probably just have a css class div.style.width = this.tileSize.width + 'px'; div.style.height = this.tileSize.height + 'px'; div.style.fontSize = '10px'; div.style.borderStyle = 'solid'; div.style.borderWidth = '1px'; div.style.borderColor = '#AAAAAA'; return div; }; }, /** * Quote an arbitrary string for use in a regex matcher. * WTB parametized regexes, JavaScript... * * From http://kevin.vanzonneveld.net * original by: booeyOH * improved by: Ates Goral (http://magnetiq.com) * improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) * bugfixed by: Onno Marsman * example 1: preg_quote("$40"); * returns 1: '\$40' * example 2: preg_quote("*RRRING* Hello?"); * returns 2: '\*RRRING\* Hello\?' * example 3: preg_quote("\\.+*?[^]$(){}=!<>|:"); * returns 3: '\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:' */ "pregQuote": function(str) { return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1"); }, /** * Setup the varous mapTypes before we actually create the map. This used * to be a bunch of crap down at the bottom of functions.js */ 'initializeMapTypes': function() { var mapOptions = {}; for (i in overviewerConfig.mapTypes) { var view = overviewerConfig.mapTypes[i]; var imageFormat = view.imgformat ? view.imgformat : 'png'; mapOptions[view.label] = { 'getTileUrl': overviewer.gmap.getTileUrlGenerator(view.path, view.base, imageFormat), 'tileSize': new google.maps.Size( overviewerConfig.CONST.tileSize, overviewerConfig.CONST.tileSize), 'maxZoom': overviewerConfig.map.maxZoom, 'minZoom': overviewerConfig.map.minZoom, 'isPng': imageFormat.toLowerCase() == 'png' } overviewer.collections.mapTypes[view.label] = new google.maps.ImageMapType( mapOptions[view.label]); overviewer.collections.mapTypes[view.label].name = view.label; overviewer.collections.mapTypes[view.label].alt = 'Minecraft ' + view.label + ' Map'; overviewer.collections.mapTypes[view.label].projection = new overviewer.classes.MapProjection(); if (view.overlay) { overviewer.collections.overlays.push( overviewer.collections.mapTypes[view.label]); } else { overviewer.collections.mapTypeIds.push( overviewerConfig.CONST.mapDivId + view.label); } } }, /** * This is where the magic happens. We setup the map with all it's * options. The query string is also parsed here so we can know if * we should be looking at a particular point on the map or just use * the default view. */ 'initializeMap': function() { var defaultCenter = overviewer.util.fromWorldToLatLng( overviewerConfig.map.center[0], overviewerConfig.map.center[1], overviewerConfig.map.center[2]); var lat = defaultCenter.lat(); var lng = defaultCenter.lng(); var zoom = overviewerConfig.map.defaultZoom; var mapcenter; var queryParams = overviewer.util.parseQueryString(); if (queryParams.lat) { lat = parseFloat(queryParams.lat); } if (queryParams.lng) { lng = parseFloat(queryParams.lng); } if (queryParams.zoom) { if (queryParams.zoom == 'max') { zoom = overviewerConfig.map.maxZoom; } else if (queryParams.zoom == 'min') { zoom = overviewerConfig.map.minZoom; } else { zoom = parseInt(queryParams.zoom); if (zoom < 0 && zoom + overviewerConfig.map.maxZoom >= 0) { //if zoom is negative, try to treat as "zoom out from max zoom" zoom += overviewerConfig.map.maxZoom; } else { //fall back to default zoom zoom = overviewerConfig.map.defaultZoom; } } } if (queryParams.x && queryParams.y && queryParams.z) { mapcenter = overviewer.util.fromWorldToLatLng(queryParams.x, queryParams.y, queryParams.z); // Add a market indicating the user-supplied position overviewer.collections.markerDatas.push([{ 'msg': 'Coordinates ' + queryParams.x + ', ' + queryParams.y + ', ' + queryParams.z, 'y': parseFloat(queryParams.y), 'x': parseFloat(queryParams.x), 'z': parseFloat(queryParams.z), 'type': 'querypos'}]); } else { mapcenter = new google.maps.LatLng(lat, lng); } var mapOptions = { zoom: zoom, center: mapcenter, panControl: overviewerConfig.map.controls.pan, scaleControl: false, mapTypeControl: overviewerConfig.map.controls.mapType && overviewer.collections.mapTypeIds.length > 1, mapTypeControlOptions: { mapTypeIds: overviewer.collections.mapTypeIds }, mapTypeId: overviewer.util.getDefaultMapTypeId(), streetViewControl: false, overviewMapControl: true, zoomControl: overviewerConfig.map.controls.zoom, backgroundColor: overviewer.util.getMapTypeBackgroundColor( overviewer.util.getDefaultMapTypeId()) }; overviewer.map = new google.maps.Map(document.getElementById( overviewerConfig.CONST.mapDivId), mapOptions); if (overviewerConfig.map.debug) { overviewer.map.overlayMapTypes.insertAt(0, new overviewer.classes.CoordMapType(new google.maps.Size( overviewerConfig.CONST.tileSize, overviewerConfig.CONST.tileSize))); google.maps.event.addListener(overviewer.map, 'click', function(event) { overviewer.util.debug('latLng: (' + event.latLng.lat() + ', ' + event.latLng.lng() + ')'); var pnt = overviewer.map.getProjection().fromLatLngToPoint(event.latLng); overviewer.util.debug('point: ' + pnt); var pxx = pnt.x * overviewerConfig.CONST.tileSize * Math.pow(2, overviewerConfig.map.maxZoom); var pxy = pnt.y * overviewerConfig.CONST.tileSize * Math.pow(2, overviewerConfig.map.maxZoom); overviewer.util.debug('pixel: (' + pxx + ', ' + pxy + ')'); }); } // Now attach the coordinate map type to the map's registry for (i in overviewer.collections.mapTypes) { overviewer.map.mapTypes.set(overviewerConfig.CONST.mapDivId + overviewer.collections.mapTypes[i].name, overviewer.collections.mapTypes[i]); } // Jump to the hash if given overviewer.util.initHash(); // Add live hash update listeners // Note: It is important to add them after jumping to the hash google.maps.event.addListener(overviewer.map, 'dragend', function() { overviewer.util.updateHash(); }); google.maps.event.addListener(overviewer.map, 'zoom_changed', function() { overviewer.util.updateHash(); }); // Make the link again whenever the map changes google.maps.event.addListener(overviewer.map, 'maptypeid_changed', function() { $('#'+overviewerConfig.CONST.mapDivId).css( 'background-color', overviewer.util.getMapTypeBackgroundColor( overviewer.map.getMapTypeId())); //smuggled this one in here for maptypeid hash generation --CounterPillow overviewer.util.updateHash(); }); }, /** * Read through overviewer.collections.markerDatas and create Marker * objects and stick them in overviewer.collections.markers . This * should probably be done differently at some point so that we can * support markers that change position more easily. */ 'initializeMarkers': function() { //first, give all collections an empty array to work with for (i in overviewerConfig.objectGroups.signs) { overviewer.util.debug('Found sign group: ' + overviewerConfig.objectGroups.signs[i].label); overviewer.collections.markers[ overviewerConfig.objectGroups.signs[i].label] = []; } for (i in overviewer.collections.markerDatas) { var markerData = overviewer.collections.markerDatas[i]; for (j in markerData) { var item = markerData[j]; // a default: var iconURL = ''; if (item.type == 'spawn') { // don't filter spawn, always display var marker = new google.maps.Marker({ 'position': overviewer.util.fromWorldToLatLng(item.x, item.y, item.z), 'map': overviewer.map, 'title': jQuery.trim(item.msg), 'icon': overviewerConfig.CONST.image.spawnMarker }); continue; } if (item.type == 'querypos') { // Set on page load if MC x/y/z coords are given in the // query string var marker = new google.maps.Marker({ 'position': overviewer.util.fromWorldToLatLng(item.x, item.y, item.z), 'map': overviewer.map, 'title': jQuery.trim(item.msg), 'icon': overviewerConfig.CONST.image.queryMarker }); continue; } var matched = false; for (j in overviewerConfig.objectGroups.signs) { var signGroup = overviewerConfig.objectGroups.signs[j]; var label = signGroup.label; if (signGroup.match(item)) { matched = true; // can add custom types of images for externally defined // item types, like 'command' here. if (item.type == 'sign') { iconURL = overviewerConfig.CONST.image.signMarker; } overviewer.util.debug('Sign icon: ' + signGroup.icon); if (signGroup.icon) { iconURL = signGroup.icon; } var marker = new google.maps.Marker({ 'position': overviewer.util.fromWorldToLatLng(item.x, item.y, item.z), 'map': overviewer.map, 'title': jQuery.trim(item.msg), 'icon': iconURL, 'visible': false }); item.marker = marker; overviewer.util.debug(label); overviewer.collections.markers[label].push(marker); if (item.type == 'sign') { overviewer.util.createMarkerInfoWindow(marker); } } } if (!matched) { // is this signpost doesn't match any of the groups in // config.js, add it automatically to the "__others__" group if (item.type == 'sign') { iconURL = overviewerConfig.CONST.image.signMarker; } var marker = new google.maps.Marker({ 'position': overviewer.util.fromWorldToLatLng(item.x, item.y, item.z), 'map': overviewer.map, 'title': jQuery.trim(item.msg), 'icon': iconURL, 'visible': false }); item.marker = marker; if (overviewer.collections.markers['__others__']) { overviewer.collections.markers['__others__'].push(marker); } else { overviewer.collections.markers['__others__'] = [marker]; } if (item.type == 'sign') { overviewer.util.createMarkerInfoWindow(marker, item); } } } } }, /** * Same as initializeMarkers() for the most part. */ 'initializeRegions': function() { for (i in overviewerConfig.objectGroups.regions) { overviewer.collections.regions[overviewerConfig.objectGroups.regions[i].label] = []; } for (i in overviewer.collections.regionDatas) { var regionData = overviewer.collections.regionDatas[i]; for (j in regionData) { var region = regionData[j]; // pull all the points out of the regions file. var converted = new google.maps.MVCArray(); for (k in region.path) { var point = region.path[k]; converted.push(overviewer.util.fromWorldToLatLng( point.x, point.y, point.z)); } for (k in overviewerConfig.objectGroups.regions) { var regionGroup = overviewerConfig.objectGroups.regions[k]; var clickable = regionGroup.clickable; var label = regionGroup.label; if(region.label) { var name = region.label } else { var name = 'rawr'; clickable = false; // if it doesn't have a name, we dont have to show it. } if(region.opacity) { var strokeOpacity = region.opacity; var fillOpacity = region.opacity * 0.25; } else { var strokeOpacity = region.strokeOpacity; var fillOpacity = region.fillOpacity; } if (region.closed) { var shape = new google.maps.Polygon({ 'name': name, 'clickable': clickable, 'geodesic': false, 'map': null, 'strokeColor': region.color, 'strokeOpacity': strokeOpacity, 'strokeWeight': overviewerConfig.CONST.regionStrokeWeight, 'fillColor': region.color, 'fillOpacity': fillOpacity, 'zIndex': j, 'paths': converted }); } else { var shape = new google.maps.Polyline({ 'name': name, 'clickable': clickable, 'geodesic': false, 'map': null, 'strokeColor': region.color, 'strokeOpacity': strokeOpacity, 'strokeWeight': overviewerConfig.CONST.regionStrokeWeight, 'zIndex': j, 'path': converted }); } overviewer.collections.regions[label].push(shape); if (clickable) { overviewer.util.createRegionInfoWindow(shape); } } } } }, /** * Change the map's div's background color according to the mapType's bg_color setting * * @param string mapTypeId * @return string */ 'getMapTypeBackgroundColor': function(mapTypeId) { for(i in overviewerConfig.mapTypes) { if( overviewerConfig.CONST.mapDivId + overviewerConfig.mapTypes[i].label == mapTypeId ) { overviewer.util.debug('Found background color for: ' + overviewerConfig.mapTypes[i].bg_color); return overviewerConfig.mapTypes[i].bg_color; } } }, /** * Gee, I wonder what this does. * * @param string msg */ 'debug': function(msg) { if (overviewerConfig.map.debug) { console.log(msg); } }, /** * Simple helper function to split the query string into key/value * pairs. Doesn't do any type conversion but both are lowercase'd. * * @return Object */ 'parseQueryString': function() { var results = {}; var queryString = location.search.substring(1); var pairs = queryString.split('&'); for (i in pairs) { var pos = pairs[i].indexOf('='); var key = pairs[i].substring(0,pos).toLowerCase(); var value = pairs[i].substring(pos+1).toLowerCase(); overviewer.util.debug( 'Found GET paramter: ' + key + ' = ' + value); results[key] = value; } return results; }, 'getDefaultMapTypeId': function() { return overviewer.collections.mapTypeIds[0]; }, /** * helper to get map LatLng from world coordinates takes arguments in * X, Y, Z order (arguments are *out of order*, because within the * function we use the axes like the rest of Minecraft Overviewer -- * with the Z and Y flipped from normal minecraft usage.) * * @param int x * @param int z * @param int y * * @return google.maps.LatLng */ 'fromWorldToLatLng': function(x, z, y) { // the width and height of all the highest-zoom tiles combined, // inverted var perPixel = 1.0 / (overviewerConfig.CONST.tileSize * Math.pow(2, overviewerConfig.map.maxZoom)); // This information about where the center column is may change with // a different drawing implementation -- check it again after any // drawing overhauls! // point (0, 0, 127) is at (0.5, 0.0) of tile (tiles/2 - 1, tiles/2) // so the Y coordinate is at 0.5, and the X is at 0.5 - // ((tileSize / 2) / (tileSize * 2^maxZoom)) // or equivalently, 0.5 - (1 / 2^(maxZoom + 1)) var lng = 0.5 - (1.0 / Math.pow(2, overviewerConfig.map.maxZoom + 1)); var lat = 0.5; // the following metrics mimic those in ChunkRenderer.chunk_render // in "chunk.py" or, equivalently, chunk_render in src/iterate.c // each block on X axis adds 12px to x and subtracts 6px from y lng += 12 * x * perPixel; lat -= 6 * x * perPixel; // each block on Y axis adds 12px to x and adds 6px to y lng += 12 * y * perPixel; lat += 6 * y * perPixel; // each block down along Z adds 12px to y lat += 12 * (128 - z) * perPixel; // add on 12 px to the X coordinate to center our point lng += 12 * perPixel; return new google.maps.LatLng(lat, lng); }, /** * The opposite of fromWorldToLatLng * NOTE: X, Y and Z in this function are Minecraft world definitions * (that is, X is horizontal, Y is altitude and Z is vertical). * * @param float lat * @param float lng * * @return Array */ 'fromLatLngToWorld': function(lat, lng) { // Initialize world x/y/z object to be returned var point = Array(); point.x = 0; point.y = 64; point.z = 0; // the width and height of all the highest-zoom tiles combined, // inverted var perPixel = 1.0 / (overviewerConfig.CONST.tileSize * Math.pow(2, overviewerConfig.map.maxZoom)); // Revert base positioning // See equivalent code in fromWorldToLatLng() lng -= 0.5 - (1.0 / Math.pow(2, overviewerConfig.map.maxZoom + 1)); lat -= 0.5; // I'll admit, I plugged this into Wolfram Alpha: // a = (x * 12 * r) + (z * 12 * r), b = (z * 6 * r) - (x * 6 * r) // And I don't know the math behind solving for for X and Z given // A (lng) and B (lat). But Wolfram Alpha did. :) I'd welcome // suggestions for splitting this up into long form and documenting // it. -RF point.x = (lng - 2 * lat) / (24 * perPixel) point.z = (lng + 2 * lat) / (24 * perPixel) // Adjust for the fact that we we can't figure out what Y is given // only latitude and longitude, so assume Y=64. point.x += 64; point.z -= 64; return point; }, /** * Create and draw the various map controls and other related things * like the compass, current view link, etc. */ 'createMapControls': function() { // compass rose, in the top right corner var compassDiv = document.createElement('DIV'); compassDiv.style.padding = '5px'; var compassImg = document.createElement('IMG'); compassImg.src = overviewerConfig.CONST.image.compass; compassDiv.appendChild(compassImg); compassDiv.index = 0; // add it to the map, top right. if (overviewerConfig.map.controls.compass) { overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(compassDiv); } // Spawn button var homeControlDiv = document.createElement('DIV'); var homeControl = new overviewer.classes.HomeControl(homeControlDiv); $(homeControlDiv).addClass('customControl'); homeControlDiv.index = 1; if (overviewerConfig.map.controls.spawn) { overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(homeControlDiv); } // Coords box var coordsDiv = document.createElement('DIV'); coordsDiv.id = 'coordsDiv'; coordsDiv.innerHTML = ''; overviewer.map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(coordsDiv); // Update coords on mousemove google.maps.event.addListener(overviewer.map, 'mousemove', function (event) { var worldcoords = overviewer.util.fromLatLngToWorld(event.latLng.lat(), event.latLng.lng()); coordsDiv.innerHTML = "Coords: X " + Math.round(worldcoords.x) + ", Z " + Math.round(worldcoords.z); }); // only need to create the control if there are items in the list. // as defined in config.js if (overviewerConfig.objectGroups.signs.length > 0) { // signpost display control var items = []; for (i in overviewerConfig.objectGroups.signs) { var signGroup = overviewerConfig.objectGroups.signs[i]; // don't create an option for this group if empty if (overviewer.collections.markers[signGroup.label].length == 0) { continue; } var iconURL = signGroup.icon; if(!iconURL) { iconURL = overviewerConfig.CONST.image.defaultMarker; } items.push({ 'label': signGroup.label, 'checked': signGroup.checked, 'icon': iconURL, 'action': function(n, item, checked) { jQuery.each(overviewer.collections.markers[item.label], function(i, elem) { elem.setVisible(checked); } ); overviewer.util.debug('Adding sign item: ' + item); } }); } // only create drop down if there's used options if (items.length > 0) { overviewer.util.createDropDown('Signposts', items); } } // if there are any regions data, lets show the option to hide/show them. if (overviewerConfig.objectGroups.regions.length > 0) { // region display control var items = []; for (i in overviewerConfig.objectGroups.regions) { var regionGroup = overviewerConfig.objectGroups.regions[i]; items.push({ 'label': regionGroup.label, 'checked': regionGroup.checked, 'action': function(n, item, checked) { jQuery.each(overviewer.collections.regions[item.label], function(i,elem) { // Thanks to LeastWeasel for this line! elem.setMap(checked ? overviewer.map : null); }); overviewer.util.debug('Adding region item: ' + item); } }); } overviewer.util.createDropDown('Regions', items); } if (overviewer.collections.overlays.length > 0) { // overlay maps control var items = []; for (i in overviewer.collections.overlays) { var overlay = overviewer.collections.overlays[i]; items.push({ 'label': overlay.name, 'checked': false, 'overlay': overlay, 'action': function(i, item, checked) { if (checked) { overviewer.map.overlayMapTypes.push(item.overlay); } else { var idx_to_delete = -1; overviewer.map.overlayMapTypes.forEach(function(e, j) { if (e == item.overlay) { idx_to_delete = j; } }); if (idx_to_delete >= 0) { overviewer.map.overlayMapTypes.removeAt(idx_to_delete); } } } }); } overviewer.util.createDropDown('Overlays', items); } }, /** * Reusable method for creating drop-down menus * * @param string title * @param array items */ 'createDropDown': function(title, items) { var control = document.createElement('DIV'); // let's let a style sheet do most of the styling here $(control).addClass('customControl'); var controlText = document.createElement('DIV'); controlText.innerHTML = title; var controlBorder = document.createElement('DIV'); $(controlBorder).addClass('top'); control.appendChild(controlBorder); controlBorder.appendChild(controlText); var dropdownDiv = document.createElement('DIV'); $(dropdownDiv).addClass('dropDown'); control.appendChild(dropdownDiv); dropdownDiv.innerHTML=''; // add the functionality to toggle visibility of the items $(controlText).click(function() { $(controlBorder).toggleClass('top-active'); $(dropdownDiv).toggle(); }); // add that control box we've made back to the map. overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(control); for(i in items) { // create the visible elements of the item var item = items[i]; overviewer.util.debug(item); var itemDiv = document.createElement('div'); var itemInput = document.createElement('input'); itemInput.type='checkbox'; // give it a name $(itemInput).data('label',item.label); jQuery(itemInput).click((function(local_idx, local_item) { return function(e) { item.action(local_idx, local_item, e.target.checked); }; })(i, item)); // if its checked, its gotta do something, do that here. if (item.checked) { itemInput.checked = true; item.action(i, item, item.checked); } dropdownDiv.appendChild(itemDiv); itemDiv.appendChild(itemInput); var textNode = document.createElement('text'); if(item.icon) { textNode.innerHTML = '' + item.label + '
'; } else { textNode.innerHTML = item.label + '
'; } itemDiv.appendChild(textNode); } }, /** * Create search box and dropdown in the top right google maps area. */ 'createSearchBox': function() { var searchControl = document.createElement("div"); searchControl.id = "searchControl"; var searchInput = document.createElement("input"); searchInput.type = "text"; searchControl.appendChild(searchInput); var searchDropDown = document.createElement("div"); searchDropDown.id = "searchDropDown"; searchControl.appendChild(searchDropDown); $(searchInput).keyup(function(e) { var newline_stripper = new RegExp("[\\r\\n]", "g") if(searchInput.value.length !== 0) { $(searchDropDown).fadeIn(); $(searchDropDown).empty(); overviewer.collections.markerDatas.forEach(function(marker_list) { marker_list.forEach(function(sign) { var regex = new RegExp(overviewer.util.pregQuote(searchInput.value), "mi"); if(sign.msg.match(regex)) { if(sign.marker !== undefined && sign.marker.getVisible()) { var t = document.createElement("div"); t.className = "searchResultItem"; var i = document.createElement("img"); i.src = sign.marker.getIcon(); t.appendChild(i); var s = document.createElement("span"); $(s).text(sign.msg.replace(newline_stripper, "")); t.appendChild(s); searchDropDown.appendChild(t); $(t).click(function(e) { $(searchDropDown).fadeOut(); overviewer.map.setZoom(7); overviewer.map.setCenter(sign.marker.getPosition()); }); } } }); }); } else { $(searchDropDown).fadeOut(); } }); overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(searchControl); }, /** * Create the pop-up infobox for when you click on a region, this can't * be done in-line because of stupid Javascript scoping problems with * closures or something. * * @param google.maps.Polygon|google.maps.Polyline shape */ 'createRegionInfoWindow': function(shape) { var infowindow = new google.maps.InfoWindow(); google.maps.event.addListener(shape, 'click', function(event, i) { if (overviewer.collections.infoWindow) { overviewer.collections.infoWindow.close(); } // Replace our Info Window's content and position var point = overviewer.util.fromLatLngToWorld(event.latLng.lat(),event.latLng.lng()); var contentString = 'Region: ' + shape.name + '
' + 'Clicked Location:
' + Math.round(point.x,1) + ', ' + point.y + ', ' + Math.round(point.z,1) + '
'; infowindow.setContent(contentString); infowindow.setPosition(event.latLng); infowindow.open(overviewer.map); overviewer.collections.infoWindow = infowindow; }); }, /** * Same as createRegionInfoWindow() * * @param google.maps.Marker marker */ 'createMarkerInfoWindow': function(marker) { var windowContent = '

' + marker.title.replace(/\n/g,'
') + '

'; var infowindow = new google.maps.InfoWindow({ 'content': windowContent }); google.maps.event.addListener(marker, 'click', function() { if (overviewer.collections.infoWindow) { overviewer.collections.infoWindow.close(); } infowindow.open(overviewer.map, marker); overviewer.collections.infoWindow = infowindow; }); }, 'initHash': function() { if(window.location.hash.split("/").length > 1) { overviewer.util.goToHash(); // Clean up the hash. overviewer.util.updateHash(); // Add a marker indicating the user-supplied position var coordinates = overviewer.util.fromLatLngToWorld(overviewer.map.getCenter().lat(), overviewer.map.getCenter().lng()); overviewer.collections.markerDatas.push([{ 'msg': 'Coordinates ' + Math.floor(coordinates.x) + ', ' + Math.floor(coordinates.y) + ', ' + Math.floor(coordinates.z), 'x': coordinates.x, 'y': coordinates.y, 'z': coordinates.z, 'type': 'querypos'}]); } }, 'setHash': function(x, y, z, zoom, maptype) { window.location.replace("#/" + Math.floor(x) + "/" + Math.floor(y) + "/" + Math.floor(z) + "/" + zoom + "/" + maptype); }, 'updateHash': function() { var coordinates = overviewer.util.fromLatLngToWorld(overviewer.map.getCenter().lat(), overviewer.map.getCenter().lng()); var zoom = overviewer.map.getZoom(); var maptype = overviewer.map.getMapTypeId(); if (zoom == overviewerConfig.map.maxZoom) { zoom = 'max'; } else if (zoom == overviewerConfig.map.minZoom) { zoom = 'min'; } else { // default to (map-update friendly) negative zooms zoom -= overviewerConfig.map.maxZoom; } overviewer.util.setHash(coordinates.x, coordinates.y, coordinates.z, zoom, maptype); }, 'goToHash': function() { // Note: the actual data begins at coords[1], coords[0] is empty. var coords = window.location.hash.split("/"); var latlngcoords = overviewer.util.fromWorldToLatLng(parseInt(coords[1]), parseInt(coords[2]), parseInt(coords[3])); var zoom; var maptype = ''; // The if-statements try to prevent unexpected behaviour when using incomplete hashes, e.g. older links if (coords.length > 4) { zoom = coords[4]; } if (coords.length > 5) { maptype = coords[5]; } if (zoom == 'max') { zoom = overviewerConfig.map.maxZoom; } else if (zoom == 'min') { zoom = overviewerConfig.map.minZoom; } else { zoom = parseInt(zoom); if (zoom < 0 && zoom + overviewerConfig.map.maxZoom >= 0) { // if zoom is negative, treat it as a "zoom out from max" zoom += overviewerConfig.map.maxZoom; } else { // fall back to default zoom zoom = overviewerConfig.map.defaultZoom; } } // If the maptype isn't set, set the default one. if (maptype == '') { // We can now set the map to use the 'coordinate' map type overviewer.map.setMapTypeId(overviewer.util.getDefaultMapTypeId()); } else { overviewer.map.setMapTypeId(maptype); } overviewer.map.setCenter(latlngcoords); overviewer.map.setZoom(zoom); }, }, /** * The various classes needed in this file. */ 'classes': { /** * This is the button that centers the map on spawn. Not sure why we * need a separate class for this and not some of the other controls. * * @param documentElement controlDiv */ 'HomeControl': function(controlDiv) { controlDiv.style.padding = '5px'; // Set CSS for the control border var control = document.createElement('DIV'); $(control).addClass('top'); control.title = 'Click to center the map on the Spawn'; controlDiv.appendChild(control); // Set CSS for the control interior var controlText = document.createElement('DIV'); controlText.innerHTML = 'Spawn'; $(controlText).addClass('button'); control.appendChild(controlText); // Setup the click event listeners: simply set the map to map center // as definned below google.maps.event.addDomListener(control, 'click', function() { overviewer.map.panTo(overviewer.util.fromWorldToLatLng( overviewerConfig.map.center[0], overviewerConfig.map.center[1], overviewerConfig.map.center[2])); overviewer.util.updateHash(); }); }, /** * Our custom projection maps Latitude to Y, and Longitude to X as * normal, but it maps the range [0.0, 1.0] to [0, tileSize] in both * directions so it is easier to position markers, etc. based on their * position (find their position in the lowest-zoom image, and divide * by tileSize) */ 'MapProjection' : function() { this.inverseTileSize = 1.0 / overviewerConfig.CONST.tileSize; }, /** * This is a mapType used only for debugging, to draw a grid on the screen * showing the tile co-ordinates and tile path. Currently the tile path * part does not work. * * @param google.maps.Size tileSize */ 'CoordMapType': function(tileSize) { this.tileSize = tileSize; } }, /** * Stuff that we give to the google maps code instead of using ourselves * goes in here. * * Also, why do I keep writing these comments as if I'm multiple people? I * should probably stop that. */ '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(overviewerConfig.map.cacheMinutes > 0) { var d = new Date(); url += '?c=' + Math.floor(d.getTime() / (1000 * 60 * overviewerConfig.map.cacheMinutes)); } return(urlBase + url); } } } };