#!/usr/bin/python2 ''' genPOI.py Scans regionsets for TileEntities and Entities, filters them, and writes out POI/marker info. A markerSet is list of POIs to display on a tileset. It has a display name, and a group name. markersDB.js holds a list of POIs in each group markers.js holds a list of which markerSets are attached to each tileSet ''' import os import logging import json import sys import re import Queue import multiprocessing from multiprocessing import Process from multiprocessing import Pool from functools import partial from optparse import OptionParser from overviewer_core import logger from overviewer_core import nbt from overviewer_core import configParser, world LOG = logging.getLogger(__name__) def replaceBads(s): "Replaces bad characters with good characters!" bads = [" ", "(", ")"] x=s for bad in bads: x = x.replace(bad,"_") return x def parseBucketChunks(bucket, rset): pid = multiprocessing.current_process().pid pois = dict(TileEntities=[], Entities=[]); i = 0 cnt = 0 l = len(bucket) for b in bucket: data = rset.get_chunk(b[0],b[1]) pois['TileEntities'] += data['TileEntities'] pois['Entities'] += data['Entities'] # Perhaps only on verbose ? i = i + 1 if i == 250: i = 0 cnt = 250 + cnt LOG.info("Found %d entities and %d tile entities in thread %d so far at %d chunks", len(pois['Entities']), len(pois['TileEntities']), pid, cnt); return pois def handleEntities(rset, outputdir, render, rname, config): # if we're already handled the POIs for this region regionset, do nothing if hasattr(rset, "_pois"): return LOG.info("Looking for entities in %r", rset) filters = render['markers'] rset._pois = dict(TileEntities=[], Entities=[]) numbuckets = config['processes']; if numbuckets < 0: numbuckets = multiprocessing.cpu_count() if numbuckets == 1: for (x,z,mtime) in rset.iterate_chunks(): data = rset.get_chunk(x,z) rset._pois['TileEntities'] += data['TileEntities'] rset._pois['Entities'] += data['Entities'] else: buckets = [[] for i in range(numbuckets)]; for (x,z,mtime) in rset.iterate_chunks(): i = x / 32 + z / 32 i = i % numbuckets buckets[i].append([x,z]) for b in buckets: LOG.info("Buckets has %d entries", len(b)); # create the partial to lock the rset dest = partial(parseBucketChunks, rset=rset) # Create a pool of processes and run all the functions pool = Pool(processes=numbuckets) results = pool.map(dest, buckets) LOG.info("All the threads completed") # Fix up all the quests in the reset for data in results: rset._pois['TileEntities'] += data['TileEntities'] rset._pois['Entities'] += data['Entities'] LOG.info("Done.") def handlePlayers(rset, render, worldpath): if not hasattr(rset, "_pois"): rset._pois = dict(TileEntities=[], Entities=[]) # only handle this region set once if 'Players' in rset._pois: return dimension = None try: dimension = {None: 0, 'DIM-1': -1, 'DIM1': 1}[rset.get_type()] except KeyError, e: mystdim = re.match(r"^DIM_MYST(\d+)$", e.message) # Dirty hack. Woo! if mystdim: dimension = int(mystdim.group(1)) else: raise playerdir = os.path.join(worldpath, "players") if os.path.isdir(playerdir): playerfiles = os.listdir(playerdir) playerfiles = [x for x in playerfiles if x.endswith(".dat")] isSinglePlayer = False else: playerfiles = [os.path.join(worldpath, "level.dat")] isSinglePlayer = True rset._pois['Players'] = [] for playerfile in playerfiles: try: data = nbt.load(os.path.join(playerdir, playerfile))[1] if isSinglePlayer: data = data['Data']['Player'] except IOError: LOG.warning("Skipping bad player dat file %r", playerfile) continue playername = playerfile.split(".")[0] if isSinglePlayer: playername = 'Player' if data['Dimension'] == dimension: # Position at last logout data['id'] = "Player" data['EntityId'] = playername data['x'] = int(data['Pos'][0]) data['y'] = int(data['Pos'][1]) data['z'] = int(data['Pos'][2]) rset._pois['Players'].append(data) if "SpawnX" in data and dimension == 0: # Spawn position (bed or main spawn) spawn = {"id": "PlayerSpawn", "EntityId": playername, "x": data['SpawnX'], "y": data['SpawnY'], "z": data['SpawnZ']} rset._pois['Players'].append(spawn) def handleManual(rset, manualpois): if not hasattr(rset, "_pois"): rset._pois = dict(TileEntities=[], Entities=[]) rset._pois['Manual'] = [] if manualpois: rset._pois['Manual'].extend(manualpois) def main(): if os.path.basename(sys.argv[0]) == """genPOI.py""": helptext = """genPOI.py %prog --config= [--quiet]""" else: helptext = """genPOI %prog --genpoi --config= [--quiet]""" logger.configure() parser = OptionParser(usage=helptext) parser.add_option("--config", dest="config", action="store", help="Specify the config file to use.") parser.add_option("--quiet", dest="quiet", action="count", help="Reduce logging output") parser.add_option("--skip-scan", dest="skipscan", action="store_true", help="Skip scanning for entities when using GenPOI") options, args = parser.parse_args() if not options.config: parser.print_help() return if options.quiet > 0: logger.configure(logging.WARN, False) # Parse the config file mw_parser = configParser.MultiWorldParser() mw_parser.parse(options.config) try: config = mw_parser.get_validated_config() except Exception: LOG.exception("An error was encountered with your configuration. See the info below.") return 1 destdir = config['outputdir'] # saves us from creating the same World object over and over again worldcache = {} markersets = set() markers = dict() for rname, render in config['renders'].iteritems(): try: worldpath = config['worlds'][render['world']] except KeyError: LOG.error("Render %s's world is '%s', but I could not find a corresponding entry in the worlds dictionary.", rname, render['world']) return 1 render['worldname_orig'] = render['world'] render['world'] = worldpath # find or create the world object if (render['world'] not in worldcache): w = world.World(render['world']) worldcache[render['world']] = w else: w = worldcache[render['world']] rset = w.get_regionset(render['dimension'][1]) if rset == None: # indicates no such dimension was found: LOG.error("Sorry, you requested dimension '%s' for the render '%s', but I couldn't find it", render['dimension'][0], rname) return 1 for f in render['markers']: markersets.add(((f['name'], f['filterFunction']), rset)) name = replaceBads(f['name']) + hex(hash(f['filterFunction']))[-4:] + "_" + hex(hash(rset))[-4:] to_append = dict(groupName=name, displayName = f['name'], icon=f.get('icon', 'signpost_icon.png'), createInfoWindow=f.get('createInfoWindow',True), checked = f.get('checked', False)) try: l = markers[rname] l.append(to_append) except KeyError: markers[rname] = [to_append] if not options.skipscan: handleEntities(rset, os.path.join(destdir, rname), render, rname, config) handlePlayers(rset, render, worldpath) handleManual(rset, render['manualpois']) LOG.info("Done handling POIs") LOG.info("Writing out javascript files") markerSetDict = dict() for (flter, rset) in markersets: # generate a unique name for this markerset. it will not be user visible filter_name = flter[0] filter_function = flter[1] name = replaceBads(filter_name) + hex(hash(filter_function))[-4:] + "_" + hex(hash(rset))[-4:] markerSetDict[name] = dict(created=False, raw=[], name=filter_name) for poi in rset._pois['Entities']: result = filter_function(poi) if result: if isinstance(result, basestring): d = dict(x=poi['Pos'][0], y=poi['Pos'][1], z=poi['Pos'][2], text=result, hovertext=result) elif type(result) == tuple: d = dict(x=poi['Pos'][0], y=poi['Pos'][1], z=poi['Pos'][2], text=result[1], hovertext=result[0]) if "icon" in poi: d.update({"icon": poi['icon']}) if "createInfoWindow" in poi: d.update({"createInfoWindow": poi['createInfoWindow']}) markerSetDict[name]['raw'].append(d) for poi in rset._pois['TileEntities']: result = filter_function(poi) if result: if isinstance(result, basestring): d = dict(x=poi['x'], y=poi['y'], z=poi['z'], text=result, hovertext=result) elif type(result) == tuple: d = dict(x=poi['x'], y=poi['y'], z=poi['z'], text=result[1], hovertext=result[0]) # Dict support to allow more flexible things in the future as well as polylines on the map. elif type(result) == dict: d = dict(x=poi['x'], y=poi['y'], z=poi['z'], text=result['text']) # Use custom hovertext if provided... if 'hovertext' in result and isinstance(result['hovertext'], basestring): d['hovertext'] = result['hovertext'] else: # ...otherwise default to display text. d['hovertext'] = result['text'] if 'polyline' in result and type(result['polyline']) == tuple: #if type(result.get('polyline', '')) == tuple: d['polyline'] = [] for point in result['polyline']: # This poor man's validation code almost definately needs improving. if type(point) == dict: d['polyline'].append(dict(x=point['x'],y=point['y'],z=point['z'])) if isinstance(result['color'], basestring): d['strokeColor'] = result['color'] if "icon" in poi: d.update({"icon": poi['icon']}) if "createInfoWindow" in poi: d.update({"createInfoWindow": poi['createInfoWindow']}) markerSetDict[name]['raw'].append(d) for poi in rset._pois['Players']: result = filter_function(poi) if result: if isinstance(result, basestring): d = dict(x=poi['x'], y=poi['y'], z=poi['z'], text=result, hovertext=result) elif type(result) == tuple: d = dict(x=poi['x'], y=poi['y'], z=poi['z'], text=result[1], hovertext=result[0]) # Dict support to allow more flexible things in the future as well as polylines on the map. elif type(result) == dict: d = dict(x=poi['x'], y=poi['y'], z=poi['z'], text=result['text']) # Use custom hovertext if provided... if 'hovertext' in result and isinstance(result['hovertext'], basestring): d['hovertext'] = result['hovertext'] else: # ...otherwise default to display text. d['hovertext'] = result['text'] if 'polyline' in result and type(result['polyline']) == tuple: #if type(result.get('polyline', '')) == tuple: d['polyline'] = [] for point in result['polyline']: # This poor man's validation code almost definately needs improving. if type(point) == dict: d['polyline'].append(dict(x=point['x'],y=point['y'],z=point['z'])) if isinstance(result['color'], basestring): d['strokeColor'] = result['color'] if "icon" in poi: d.update({"icon": poi['icon']}) if "createInfoWindow" in poi: d.update({"createInfoWindow": poi['createInfoWindow']}) markerSetDict[name]['raw'].append(d) for poi in rset._pois['Manual']: result = filter_function(poi) if result: if isinstance(result, basestring): d = dict(x=poi['x'], y=poi['y'], z=poi['z'], text=result, hovertext=result) elif type(result) == tuple: d = dict(x=poi['x'], y=poi['y'], z=poi['z'], text=result[1], hovertext=result[0]) # Dict support to allow more flexible things in the future as well as polylines on the map. elif type(result) == dict: d = dict(x=poi['x'], y=poi['y'], z=poi['z'], text=result['text']) # Use custom hovertext if provided... if 'hovertext' in result and isinstance(result['hovertext'], basestring): d['hovertext'] = result['hovertext'] else: # ...otherwise default to display text. d['hovertext'] = result['text'] if 'polyline' in result and type(result['polyline']) == tuple: #if type(result.get('polyline', '')) == tuple: d['polyline'] = [] for point in result['polyline']: # This poor man's validation code almost definately needs improving. if type(point) == dict: d['polyline'].append(dict(x=point['x'],y=point['y'],z=point['z'])) if isinstance(result['color'], basestring): d['strokeColor'] = result['color'] if "icon" in poi: d.update({"icon": poi['icon']}) if "createInfoWindow" in poi: d.update({"createInfoWindow": poi['createInfoWindow']}) markerSetDict[name]['raw'].append(d) #print markerSetDict with open(os.path.join(destdir, "markersDB.js"), "w") as output: output.write("var markersDB=") json.dump(markerSetDict, output, indent=2) output.write(";\n"); with open(os.path.join(destdir, "markers.js"), "w") as output: output.write("var markers=") json.dump(markers, output, indent=2) output.write(";\n"); with open(os.path.join(destdir, "baseMarkers.js"), "w") as output: output.write("overviewer.util.injectMarkerScript('markersDB.js');\n") output.write("overviewer.util.injectMarkerScript('markers.js');\n") output.write("overviewer.collections.haveSigns=true;\n") LOG.info("Done") if __name__ == "__main__": main()