diff --git a/config.js b/config.js new file mode 100644 index 0000000..45534c6 --- /dev/null +++ b/config.js @@ -0,0 +1,28 @@ + + var config = { + path: 'tiles', + fileExt: '{imgformat}', + tileSize: 384, + defaultZoom: 1, + maxZoom: {maxzoom}, + cacheMinutes: 0, // Change this to have browsers automatically request new images every x minutes + debug: false + }; + + +// define a list of pattern-label pairs. Each label will appear +// in the 'Signposts' control, allowing your users to quickly enable +// or disable certain labels. See below for some examples: +var signGroups = { +// "Directions": /^#Direction/i, +// "Big Dig": /big\s*dig/i, +// "Warnings": /warning/i, +}; + +// Please leave the following variables here: +var markerCollection = {}; // holds groups of markers + +var map; + +var markersInit = false; +var regionsInit = false; diff --git a/quadtree.py b/quadtree.py index ef00361..ead65e8 100644 --- a/quadtree.py +++ b/quadtree.py @@ -125,19 +125,20 @@ class QuadtreeGen(object): complete, total, level, self.p)) def write_html(self, skipjs=False): - """Writes out index.html, marker.js, and region.js""" + """Writes out config.js, marker.js, and region.js + Copies web assets into the destdir""" zoomlevel = self.p imgformat = self.imgformat - templatepath = os.path.join(util.get_program_path(), "template.html") + configpath = os.path.join(util.get_program_path(), "config.js") - html = open(templatepath, 'r').read() - html = html.replace( + config = open(configpath, 'r').read() + config = config.replace( "{maxzoom}", str(zoomlevel)) - html = html.replace( + config = config.replace( "{imgformat}", str(imgformat)) - with open(os.path.join(self.destdir, "index.html"), 'w') as output: - output.write(html) + with open(os.path.join(self.destdir, "config.js"), 'w') as output: + output.write(config) # Write a blank image blank = Image.new("RGBA", (1,1)) @@ -181,11 +182,11 @@ class QuadtreeGen(object): def _get_cur_depth(self): """How deep is the quadtree currently in the destdir? This glances in - index.html to see what maxZoom is set to. + config.js to see what maxZoom is set to. returns -1 if it couldn't be detected, file not found, or nothing in - index.html matched + config.js matched """ - indexfile = os.path.join(self.destdir, "index.html") + indexfile = os.path.join(self.destdir, "config.js") if not os.path.exists(indexfile): return -1 matcher = re.compile(r"maxZoom:\s*(\d+)") diff --git a/template.html b/template.html deleted file mode 100644 index 99b33ca..0000000 --- a/template.html +++ /dev/null @@ -1,276 +0,0 @@ - - - - - - - - - - - -
- - diff --git a/web_assets/functions.js b/web_assets/functions.js new file mode 100644 index 0000000..31e9249 --- /dev/null +++ b/web_assets/functions.js @@ -0,0 +1,372 @@ + +function prepareSignMarker(marker, item) { + + var c = "

" + item.msg.replace(/\n/g,"
") + "

"; + var infowindow = new google.maps.InfoWindow({content: c + }); + google.maps.event.addListener(marker, 'click', function() { + infowindow.open(map,marker); + }); + +} + + +function drawMapControls() { + + // compass rose, in the top right corner + var compassDiv = document.createElement('DIV'); + + compassDiv.style.padding = '5px'; + + var compassImg = document.createElement('IMG'); + compassImg.src="compass.png"; + compassDiv.appendChild(compassImg); + + map.controls[google.maps.ControlPosition.TOP_RIGHT].push(compassDiv); + + + // signpost display control + // + + var signControl = document.createElement("DIV"); + signControl.id = "signControl"; // let's let a style sheet do most of the styling here + + var controlBorder = document.createElement("DIV"); + controlBorder.id="top"; + signControl.appendChild(controlBorder); + + var controlText = document.createElement("DIV"); + + controlBorder.appendChild(controlText); + + controlText.innerHTML = "Signposts"; + + var dropdownDiv = document.createElement("DIV"); + + + $(controlText).click(function() { + $(dropdownDiv).toggle(); + + }); + + + dropdownDiv.id="dropDown"; + signControl.appendChild(dropdownDiv); + dropdownDiv.innerHTML=""; + + map.controls[google.maps.ControlPosition.TOP_RIGHT].push(signControl); + + + + for (label in signGroups) { + var n = document.createElement("input"); + n.type="checkbox"; + + $(n).data("label",label); + jQuery(n).click(function(e) { + var t = $(e.target); + jQuery.each(markerCollection[t.data("label")], function(i,elem) {elem.setVisible(e.target.checked);}); + }); + + dropdownDiv.appendChild(n); + var textNode = document.createElement("text"); + textNode.innerHTML = label + "
"; + dropdownDiv.appendChild(textNode); + + } + + // add "others" + var n = document.createElement("input"); + n.type="checkbox"; + + $(n).data("label","__others__"); + jQuery(n).click(function(e) { + var t = $(e.target); + jQuery.each(markerCollection[t.data("label")], function(i,elem) {elem.setVisible(e.target.checked);}); + }); + + dropdownDiv.appendChild(n); + var textNode = document.createElement("text"); + textNode.innerHTML = "Others
"; + dropdownDiv.appendChild(textNode); +} + +function initRegions() { + if (regionsInit) { return; } + + regionsInit = true; + + for (i in regionData) { + var region = regionData[i]; + var converted = new google.maps.MVCArray(); + for (j in region.path) { + var point = region.path[j]; + converted.push(fromWorldToLatLng(point.x, point.y, point.z)); + } + + if (region.closed) { + new google.maps.Polygon({clickable: false, + geodesic: false, + map: map, + strokeColor: region.color, + strokeOpacity: region.opacity, + strokeWeight: 2, + fillColor: region.color, + fillOpacity: region.opacity * 0.25, + zIndex: i, + paths: converted + }); + } else { + new google.maps.Polyline({clickable: false, + geodesic: false, + map: map, + strokeColor: region.color, + strokeOpacity: region.opacity, + strokeWeight: 2, + zIndex: i, + path: converted + }); + } + } +} + + + +function initMarkers() { + if (markersInit) { return; } + + markersInit = true; + + for (i in markerData) { + var item = markerData[i]; + + // a default: + var iconURL = ''; + if (item.type == 'spawn') { + // don't filter spawn, always display + + iconURL = 'http://google-maps-icons.googlecode.com/files/home.png'; + var converted = fromWorldToLatLng(item.x, item.y, item.z); + var marker = new google.maps.Marker({position: converted, + map: map, + title: item.msg, + icon: iconURL + }); + + continue; + } + + var matched = false; + for (label in signGroups) { + var pattern = signGroups[label]; + + var r = item.msg.match(pattern); + if (r) { + matched = true; + + if (item.type == 'sign') { iconURL = 'signpost_icon.png';} + + var converted = fromWorldToLatLng(item.x, item.y, item.z); + var marker = new google.maps.Marker({position: converted, + map: map, + title: item.msg, + icon: iconURL, + visible: false + }); + if (markerCollection[label]) { + markerCollection[label].push(marker); + } else { + markerCollection[label] = [marker]; + } + + if (item.type == 'sign') { + prepareSignMarker(marker, item); + } + + } + + + } + + 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 = 'signpost_icon.png';} + + var converted = fromWorldToLatLng(item.x, item.y, item.z); + var marker = new google.maps.Marker({position: converted, + map: map, + title: item.msg, + icon: iconURL, + visible: false + }); + if (markerCollection["__others__"]) { + markerCollection["__others__"].push(marker); + } else { + markerCollection["__others__"] = [marker]; + } + + if (item.type == 'sign') { + prepareSignMarker(marker, item); + } + } + + + } +} + + + +function initialize() { + var mapOptions = {zoom: config.defaultZoom, + center: new google.maps.LatLng(0.5, 0.5), + navigationControl: true, + scaleControl: false, + mapTypeControl: false, + streetViewControl: false, + mapTypeId: 'mcmap' + }; + map = new google.maps.Map(document.getElementById("mcmap"), mapOptions); + + if(config.debug) { + map.overlayMapTypes.insertAt(0, new CoordMapType(new google.maps.Size(config.tileSize, config.tileSize))); + + google.maps.event.addListener(map, 'click', function(event) { + console.log("latLng; " + event.latLng.lat() + ", " + event.latLng.lng()); + + var pnt = map.getProjection().fromLatLngToPoint(event.latLng); + console.log("point: " + pnt); + + var pxx = pnt.x * config.tileSize * Math.pow(2, config.maxZoom); + var pxy = pnt.y * config.tileSize * Math.pow(2, config.maxZoom); + console.log("pixel: " + pxx + ", " + pxy); + }); + } + + // Now attach the coordinate map type to the map's registry + map.mapTypes.set('mcmap', MCMapType); + + // We can now set the map to use the 'coordinate' map type + map.setMapTypeId('mcmap'); + + + // initialize the markers and regions + initMarkers(); + initRegions(); + drawMapControls(); + + + +} + + + // 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) + function MCMapProjection() { + this.inverseTileSize = 1.0 / config.tileSize; + } + + MCMapProjection.prototype.fromLatLngToPoint = function(latLng) { + var x = latLng.lng() * config.tileSize; + var y = latLng.lat() * config.tileSize; + return new google.maps.Point(x, y); + }; + + MCMapProjection.prototype.fromPointToLatLng = function(point) { + var lng = point.x * this.inverseTileSize; + var lat = point.y * this.inverseTileSize; + return new google.maps.LatLng(lat, lng); + }; + + // 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.) + function fromWorldToLatLng(x, z, y) + { + // the width and height of all the highest-zoom tiles combined, inverted + var perPixel = 1.0 / (config.tileSize * Math.pow(2, config.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, config.maxZoom + 1)); + var lat = 0.5; + + // the following metrics mimic those in ChunkRenderer.chunk_render in "chunk.py" + + // 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 and 18px to the Y to center our point + lng += 12 * perPixel; + lat += 18 * perPixel; + + return new google.maps.LatLng(lat, lng); + } + + var MCMapOptions = { + getTileUrl: function(tile, zoom) { + var url = config.path; + 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 + '.' + config.fileExt; + if(config.cacheMinutes > 0) { + var d = new Date(); + url += '?c=' + Math.floor(d.getTime() / (1000 * 60 * config.cacheMinutes)); + } + return(url); + }, + tileSize: new google.maps.Size(config.tileSize, config.tileSize), + maxZoom: config.maxZoom, + minZoom: 0, + isPng: !(config.fileExt.match(/^png$/i) == null) + }; + + var MCMapType = new google.maps.ImageMapType(MCMapOptions); + MCMapType.name = "MC Map"; + MCMapType.alt = "Minecraft Map"; + MCMapType.projection = new MCMapProjection(); + + function CoordMapType() { + } + + function CoordMapType(tileSize) { + this.tileSize = tileSize; + } + + CoordMapType.prototype.getTile = function(coord, zoom, ownerDocument) { + var div = ownerDocument.createElement('DIV'); + div.innerHTML = "(" + coord.x + ", " + coord.y + ", " + zoom + ")"; + div.innerHTML += "
"; + div.innerHTML += MCMapOptions.getTileUrl(coord, zoom); + div.style.width = this.tileSize.width + 'px'; + div.style.height = this.tileSize.height + 'px'; + div.style.fontSize = '10'; + div.style.borderStyle = 'solid'; + div.style.borderWidth = '1px'; + div.style.borderColor = '#AAAAAA'; + return div; + }; diff --git a/web_assets/index.html b/web_assets/index.html new file mode 100644 index 0000000..e77fee7 --- /dev/null +++ b/web_assets/index.html @@ -0,0 +1,18 @@ + + + + + + + + + + + + + +
+ + diff --git a/web_assets/style.css b/web_assets/style.css index 6b47816..6b8db7b 100644 --- a/web_assets/style.css +++ b/web_assets/style.css @@ -16,3 +16,27 @@ body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; } text-align: center; font-family: monospace; } + +#signControl { + padding: 5px; + height: 15px; + font-family: Arial, sans-serif; +} + +#signControl > div#top { + background-color: #fff; + border: 1px solid #000; + text-align: center; + width: 70px; + font-size: 12px; + width: 70px; + cursor: pointer; +} + +#signControl > div#dropDown { + border: 1px solid #000; + font-size: 12px; + background-color: #fff; + display: none; +} +