diff --git a/.gitignore b/.gitignore index 86d813a..621c845 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ build terrain.png cachedir* +# user-provided settings file +settings.py + # header files that may be copied over, if missing ImPlatform.h Imaging.h diff --git a/README.rst b/README.rst index 3164ef7..5acc920 100644 --- a/README.rst +++ b/README.rst @@ -121,9 +121,9 @@ the same directory as "_composite.c". Running ------- -To generate a set of Google Map tiles, use the gmap.py script like this:: +To generate a set of Google Map tiles, use the overviewer.py script like this:: - python gmap.py [OPTIONS] + python overviewer.py [OPTIONS] The output directory will be created if it doesn't exist. This will generate a set of image tiles for your world in the directory you choose. When it's done, @@ -153,7 +153,7 @@ Options Example:: - python gmap.py --cachedir= + python overviewer.py --cachedir= --imgformat=FORMAT Set the output image format used for the tiles. The default is 'png', @@ -168,7 +168,7 @@ Options Example to run 5 worker processes in parallel:: - python gmap.py -p 5 + python overviewer.py -p 5 -z ZOOM, --zoom=ZOOM The Overviewer by default will detect how many zoom levels are required @@ -191,7 +191,7 @@ Options This will render your map with 7 zoom levels:: - python gmap.py -z 7 + python overviewer.py -z 7 Remember that each additional zoom level adds 4 times as many tiles as the last. This can add up fast, zoom level 10 has over a million tiles. @@ -210,13 +210,13 @@ Options By default, the chunk images are saved in your world directory. This example will remove them:: - python gmap.py -d + python overviewer.py -d You can also delete the tile cache as well. This will force a full re-render, useful if you've changed texture packs and want your world to look uniform. Here's an example:: - python gmap.py -d <# / path> + python overviewer.py -d <# / path> Be warned, this will cause the next rendering of your map to take significantly longer, since it is having to re-generate the files you just diff --git a/config.js b/config.js index c841130..9296853 100644 --- a/config.js +++ b/config.js @@ -39,13 +39,16 @@ var signGroups = [ * path : string. Location of the rendered tiles. * Optional: * base : string. Base of the url path for tile locations, useful for serving tiles from a different server than the js/html server. - */ + var mapTypeData=[ {'label': 'Unlit', 'path': 'tiles'}, // {'label': 'Day', 'path': 'lighting/tiles'}, // {'label': 'Night', 'path': 'night/tiles'}, // {'label': 'Spawn', 'path': 'spawn/tiles', 'base': 'http://example.cdn.amazon.com/'} ]; + */ + +var mapTypeData = {maptypedata}; // Please leave the following variables here: var markerCollection = {}; // holds groups of markers diff --git a/contrib/clearOldCache.py b/contrib/clearOldCache.py index 18b402b..01b604d 100644 --- a/contrib/clearOldCache.py +++ b/contrib/clearOldCache.py @@ -4,7 +4,7 @@ usage = "python contrib/%prog [OPTIONS] " description = """ This script will delete files from the old chunk-based cache, a lot -like the old `gmap.py -d World/` command. You should only use this if +like the old `overviewer.py -d World/` command. You should only use this if you're updating from an older version of Overviewer, and you want to clean up your world folder. """ @@ -19,7 +19,7 @@ overviewer_dir = os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] sys.path.insert(0, overviewer_dir) import world -from gmap import list_worlds +from overviewer import list_worlds def main(): parser = OptionParser(usage=usage, description=description) diff --git a/contrib/findSigns.py b/contrib/findSigns.py index a5bfa5d..c390654 100644 --- a/contrib/findSigns.py +++ b/contrib/findSigns.py @@ -11,7 +11,7 @@ To run, simply give a path to your world directory, for example: Once that is done, simply re-run the overviewer to generate markers.js: - python gmap.py ../world.test/ output_dir/ + python overviewer.py ../world.test/ output_dir/ Note: if your cachedir is not the same as your world-dir, you'll need to manually move overviewer.dat into the correct location. diff --git a/contrib/rerenderBlocks.py b/contrib/rerenderBlocks.py index 19ab178..51940df 100644 --- a/contrib/rerenderBlocks.py +++ b/contrib/rerenderBlocks.py @@ -3,12 +3,12 @@ ''' This is used to force the regeneration of any chunks that contain a certain blockID. The output is a chunklist file that is suitable to use with the ---chunklist option to gmap.py. +--chunklist option to overviewer.py. Example: python contrib/rerenderBlocks.py --ids=46,79,91 --world=world/> chunklist.txt - python gmap.py --chunklist=chunklist.txt world/ output_dir/ + python overviewer.py --chunklist=chunklist.txt world/ output_dir/ This will rerender any chunks that contain either TNT (46), Ice (79), or a Jack-O-Lantern (91) diff --git a/googlemap.py b/googlemap.py new file mode 100644 index 0000000..fbecc03 --- /dev/null +++ b/googlemap.py @@ -0,0 +1,147 @@ +# This file is part of the Minecraft Overviewer. +# +# Minecraft Overviewer is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License as published +# by the Free Software Foundation, either version 3 of the License, or (at +# your option) any later version. +# +# Minecraft Overviewer is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with the Overviewer. If not, see . + +import os +import os.path +import cPickle +import Image +import shutil +from time import strftime, gmtime +import json + +import util + +""" +This module has routines related to generating a Google Maps-based +interface out of a set of tiles. + +""" + +def mirror_dir(src, dst, entities=None): + '''copies all of the entities from src to dst''' + if not os.path.exists(dst): + os.mkdir(dst) + if entities and type(entities) != list: raise Exception("Expected a list, got a %r instead" % type(entities)) + + for entry in os.listdir(src): + if entities and entry not in entities: continue + if os.path.isdir(os.path.join(src,entry)): + mirror_dir(os.path.join(src, entry), os.path.join(dst, entry)) + elif os.path.isfile(os.path.join(src,entry)): + try: + shutil.copy(os.path.join(src, entry), os.path.join(dst, entry)) + except IOError: + # maybe permission problems? + os.chmod(os.path.join(src, entry), stat.S_IRUSR) + os.chmod(os.path.join(dst, entry), stat.S_IWUSR) + shutil.copy(os.path.join(src, entry), os.path.join(dst, entry)) + # if this stills throws an error, let it propagate up + +class MapGen(object): + def __init__(self, quadtrees, skipjs=False, web_assets_hook=None): + """Generates a Google Maps interface for the given list of + quadtrees. All of the quadtrees must have the same destdir, + image format, and world.""" + + self.skipjs = skipjs + self.web_assets_hook = web_assets_hook + + if not len(quadtrees) > 0: + raise ValueError("there must be at least one quadtree to work on") + + self.destdir = quadtrees[0].destdir + self.imgformat = quadtrees[0].imgformat + self.world = quadtrees[0].world + self.p = quadtrees[0].p + for i in quadtrees: + if i.destdir != self.destdir or i.imgformat != self.imgformat or i.world != self.world: + raise ValueError("all the given quadtrees must have the same destdir") + + self.quadtrees = quadtrees + + def go(self, procs): + """Writes out config.js, marker.js, and region.js + Copies web assets into the destdir""" + zoomlevel = self.p + imgformat = self.imgformat + configpath = os.path.join(util.get_program_path(), "config.js") + + config = open(configpath, 'r').read() + config = config.replace( + "{maxzoom}", str(zoomlevel)) + config = config.replace( + "{imgformat}", str(imgformat)) + + # create generated map type data, from given quadtrees + maptypedata = map(lambda q: {'label' : q.rendermode.capitalize(), + 'path' : q.tiledir}, self.quadtrees) + config = config.replace("{maptypedata}", json.dumps(maptypedata)) + + with open(os.path.join(self.destdir, "config.js"), 'w') as output: + output.write(config) + + # Write a blank image + for quadtree in self.quadtrees: + blank = Image.new("RGBA", (1,1)) + tileDir = os.path.join(self.destdir, quadtree.tiledir) + if not os.path.exists(tileDir): os.mkdir(tileDir) + blank.save(os.path.join(tileDir, "blank."+self.imgformat)) + + # copy web assets into destdir: + mirror_dir(os.path.join(util.get_program_path(), "web_assets"), self.destdir) + + # Add time in index.html + indexpath = os.path.join(self.destdir, "index.html") + + index = open(indexpath, 'r').read() + index = index.replace( + "{time}", str(strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()))) + + with open(os.path.join(self.destdir, "index.html"), 'w') as output: + output.write(index) + + if self.skipjs: + if self.web_assets_hook: + self.web_assets_hook(self) + return + + # since we will only discover PointsOfInterest in chunks that need to be + # [re]rendered, POIs like signs in unchanged chunks will not be listed + # in self.world.POI. To make sure we don't remove these from markers.js + # we need to merge self.world.POI with the persistant data in world.PersistentData + + self.world.POI += filter(lambda x: x['type'] != 'spawn', self.world.persistentData['POI']) + + # write out the default marker table + with open(os.path.join(self.destdir, "markers.js"), 'w') as output: + output.write("var markerData=%s" % json.dumps(self.world.POI)) + + # save persistent data + self.world.persistentData['POI'] = self.world.POI + with open(self.world.pickleFile,"wb") as f: + cPickle.dump(self.world.persistentData,f) + + # write out the default (empty, but documented) region table + with open(os.path.join(self.destdir, "regions.js"), 'w') as output: + output.write('var regionData=[\n') + output.write(' // {"color": "#FFAA00", "opacity": 0.5, "closed": true, "path": [\n') + output.write(' // {"x": 0, "y": 0, "z": 0},\n') + output.write(' // {"x": 0, "y": 10, "z": 0},\n') + output.write(' // {"x": 0, "y": 0, "z": 10}\n') + output.write(' // ]},\n') + output.write('];') + + if self.web_assets_hook: + self.web_assets_hook(self) diff --git a/gmap.py b/overviewer.py similarity index 97% rename from gmap.py rename to overviewer.py index 64a4fbe..d5d17c0 100755 --- a/gmap.py +++ b/overviewer.py @@ -43,6 +43,7 @@ import optimizeimages import composite import world import quadtree +import googlemap helptext = """ %prog [OPTIONS] @@ -159,10 +160,15 @@ def main(): w = world.World(worlddir, useBiomeData=useBiomeData, lighting=options.lighting) w.go(options.procs) - # Now generate the tiles + # create the quadtrees # TODO chunklist - q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg, rendermode=options.rendermode, web_assets_hook=options.web_assets_hook) - q.write_html(options.skipjs) + q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg, rendermode=options.rendermode) + + # write out the map and web assets + m = googlemap.MapGen([q,], skipjs=options.skipjs, web_assets_hook=options.web_assets_hook) + m.go(options.procs) + + # render the tiles! q.go(options.procs) def delete_all(worlddir, tiledir): diff --git a/quadtree.py b/quadtree.py index 25855fc..7583b84 100644 --- a/quadtree.py +++ b/quadtree.py @@ -42,26 +42,6 @@ This module has routines related to generating a quadtree of tiles """ -def mirror_dir(src, dst, entities=None): - '''copies all of the entities from src to dst''' - if not os.path.exists(dst): - os.mkdir(dst) - if entities and type(entities) != list: raise Exception("Expected a list, got a %r instead" % type(entities)) - - for entry in os.listdir(src): - if entities and entry not in entities: continue - if os.path.isdir(os.path.join(src,entry)): - mirror_dir(os.path.join(src, entry), os.path.join(dst, entry)) - elif os.path.isfile(os.path.join(src,entry)): - try: - shutil.copy(os.path.join(src, entry), os.path.join(dst, entry)) - except IOError: - # maybe permission problems? - os.chmod(os.path.join(src, entry), stat.S_IRUSR) - os.chmod(os.path.join(dst, entry), stat.S_IWUSR) - shutil.copy(os.path.join(src, entry), os.path.join(dst, entry)) - # if this stills throws an error, let it propagate up - def iterate_base4(d): """Iterates over a base 4 number with d digits""" return itertools.product(xrange(4), repeat=d) @@ -90,7 +70,7 @@ def pool_initializer(quadtree): child_quadtree = quadtree class QuadtreeGen(object): - def __init__(self, worldobj, destdir, depth=None, tiledir="tiles", imgformat=None, optimizeimg=None, rendermode="normal", web_assets_hook=None): + def __init__(self, worldobj, destdir, depth=None, tiledir="tiles", imgformat=None, optimizeimg=None, rendermode="normal"): """Generates a quadtree from the world given into the given dest directory @@ -103,11 +83,11 @@ class QuadtreeGen(object): assert(imgformat) self.imgformat = imgformat self.optimizeimg = optimizeimg - self.web_assets_hook = web_assets_hook self.lighting = rendermode in ("lighting", "night", "spawn") self.night = rendermode in ("night", "spawn") self.spawn = rendermode in ("spawn",) + self.rendermode = rendermode # Make the destination dir if not os.path.exists(destdir): @@ -160,75 +140,6 @@ class QuadtreeGen(object): return logging.info("{0}/{1} tiles complete on level {2}/{3}".format( complete, total, level, self.p)) - - def write_html(self, skipjs=False): - """Writes out config.js, marker.js, and region.js - Copies web assets into the destdir""" - zoomlevel = self.p - imgformat = self.imgformat - configpath = os.path.join(util.get_program_path(), "config.js") - - config = open(configpath, 'r').read() - config = config.replace( - "{maxzoom}", str(zoomlevel)) - config = config.replace( - "{imgformat}", str(imgformat)) - - 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)) - tileDir = os.path.join(self.destdir, self.tiledir) - if not os.path.exists(tileDir): os.mkdir(tileDir) - blank.save(os.path.join(tileDir, "blank."+self.imgformat)) - - # copy web assets into destdir: - mirror_dir(os.path.join(util.get_program_path(), "web_assets"), self.destdir) - - # Add time in index.html - indexpath = os.path.join(self.destdir, "index.html") - - index = open(indexpath, 'r').read() - index = index.replace( - "{time}", str(strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()))) - - with open(os.path.join(self.destdir, "index.html"), 'w') as output: - output.write(index) - - if skipjs: - if self.web_assets_hook: - self.web_assets_hook(self) - return - - # since we will only discover PointsOfInterest in chunks that need to be - # [re]rendered, POIs like signs in unchanged chunks will not be listed - # in self.world.POI. To make sure we don't remove these from markers.js - # we need to merge self.world.POI with the persistant data in world.PersistentData - - self.world.POI += filter(lambda x: x['type'] != 'spawn', self.world.persistentData['POI']) - - # write out the default marker table - with open(os.path.join(self.destdir, "markers.js"), 'w') as output: - output.write("var markerData=%s" % json.dumps(self.world.POI)) - - # save persistent data - self.world.persistentData['POI'] = self.world.POI - with open(self.world.pickleFile,"wb") as f: - cPickle.dump(self.world.persistentData,f) - - # write out the default (empty, but documented) region table - with open(os.path.join(self.destdir, "regions.js"), 'w') as output: - output.write('var regionData=[\n') - output.write(' // {"color": "#FFAA00", "opacity": 0.5, "closed": true, "path": [\n') - output.write(' // {"x": 0, "y": 0, "z": 0},\n') - output.write(' // {"x": 0, "y": 10, "z": 0},\n') - output.write(' // {"x": 0, "y": 0, "z": 10}\n') - output.write(' // ]},\n') - output.write('];') - - if self.web_assets_hook: - self.web_assets_hook(self) def _get_cur_depth(self): """How deep is the quadtree currently in the destdir? This glances in diff --git a/setup.py b/setup.py index cf12c18..48a776d 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ setup_kwargs['cmdclass'] = {} # if py2exe is not None: - setup_kwargs['console'] = ['gmap.py'] + setup_kwargs['console'] = ['overviewer.py'] setup_kwargs['data_files'] = [('textures', ['textures/lava.png', 'textures/water.png', 'textures/fire.png']), ('', ['config.js', 'COPYING.txt', 'README.rst']), ('web_assets', glob.glob('web_assets/*'))]