/* * Copyright (C) 2006, 2012. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 or * version 2 as published by the Free Software Foundation. * * This program 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. */ package uk.me.parabola.mkgmap.reader.osm.boundary; import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingQueue; import uk.me.parabola.imgfmt.FormatException; import uk.me.parabola.log.Logger; import uk.me.parabola.mkgmap.reader.osm.LocationHook; /** * Preprocesses the boundary information to be used by the {@link LocationHook} class. * @author WanMil */ public class BoundaryPreprocessor implements Runnable { private static final Logger log = Logger.getLogger(BoundaryPreprocessor.class); private static final List<Class<? extends LoadableBoundaryDataSource>> loaders; static { String[] sources = { "uk.me.parabola.mkgmap.reader.osm.boundary.OsmBinBoundaryDataSource", "uk.me.parabola.mkgmap.reader.osm.boundary.O5mBinBoundaryDataSource", // must be last as it is the default "uk.me.parabola.mkgmap.reader.osm.boundary.Osm5BoundaryDataSource", }; loaders = new ArrayList<>(); for (String source : sources) { try { @SuppressWarnings({ "unchecked" }) Class<? extends LoadableBoundaryDataSource> c = (Class<? extends LoadableBoundaryDataSource>) Class .forName(source); loaders.add(c); } catch (ClassNotFoundException e) { // not available, try the rest } catch (NoClassDefFoundError e) { // not available, try the rest } } } /** * Return a suitable boundary map reader. The name of the resource to be * read is passed in. This is usually a file name, but could be something * else. * * @param name * The resource name to be read. * @return A LoadableBoundaryDataSource that is capable of reading the * resource. */ private static LoadableBoundaryDataSource createMapReader(String name) { for (Class<? extends LoadableBoundaryDataSource> loader : loaders) { try { LoadableBoundaryDataSource src = loader.newInstance(); if (name != null && src.isFileSupported(name)) return src; } catch (InstantiationException e) { // try the next one. } catch (IllegalAccessException e) { // try the next one. } catch (NoClassDefFoundError e) { // try the next one } } // Give up and assume it is in the XML format. If it isn't we will get // an error soon enough anyway. return new Osm5BoundaryDataSource(); } private String boundaryFilename; private String outDir; private ExecutorService threadPool; private final BlockingQueue<Future<Object>> remainingTasks = new LinkedBlockingQueue<>(); /** * constructor for stand-alone usage (workout only) * @param in source directory or zip file * @param out target directory */ private BoundaryPreprocessor(String boundaryFilename, String out){ this.boundaryFilename = boundaryFilename; this.outDir = out; int maxJobs = Runtime.getRuntime().availableProcessors(); if (maxJobs > 1) this.threadPool = Executors.newFixedThreadPool(maxJobs); else this.threadPool = null; } /** * Sets the number of threads that is used as maximum by the boundary * preparer. * * @param maxThreads the maximum number of threads */ public void setMaxThreads(int maxThreads) { if (maxThreads > 1) this.threadPool = Executors.newFixedThreadPool(maxThreads); else this.threadPool = null; } public void run() { long t1 = System.currentTimeMillis(); boolean prepOK = createRawData(); long t2 = System.currentTimeMillis(); log.info("BoundaryPreparer pass 1 took", (t2-t1), "ms"); if (!prepOK){ System.err.println("Boundary creation failed."); return; } workoutBoundaryRelations(); } /** * Parse OSM data and create boundaries. Distribute the boundaries on a grid * with a fixed raster. * @return true if data was successfully written, else false */ private boolean createRawData(){ File boundsDirectory = new File(outDir); BoundarySaver saver = new BoundarySaver(boundsDirectory, BoundarySaver.RAW_DATA_FORMAT); LoadableBoundaryDataSource dataSource = createMapReader(boundaryFilename); dataSource.setBoundarySaver(saver); log.info("Started loading", boundaryFilename); try { dataSource.load(boundaryFilename); } catch (FileNotFoundException e) { e.printStackTrace(); return false; } catch (FormatException e) { e.printStackTrace(); return false; } saver.setBbox(dataSource.getBounds()); log.info("Finished loading", boundaryFilename); saver.end(); return true; } public static void main(String[] args) { if (args[0].equals("--help") || args.length != 2) { System.err.println("Usage:"); System.err.println("java -cp mkgmap.jar uk.me.parabola.mkgmap.reader.osm.boundary.BoundaryPreprocessor <inputfile> <boundsdir>"); System.err.println(" <inputfile>: File containing boundary data (OSM, PBF or O5M format)"); System.err.println(" <boundsdir>: Directory in which the preprocessed bounds files are created"); System.exit(-1); } String inputFile = args[0]; String outputDir = args[1]; long t1 = System.currentTimeMillis(); BoundaryPreprocessor p = new BoundaryPreprocessor(inputFile, outputDir); try { p.runPreprocessing(); } catch (InterruptedException exp) { exp.printStackTrace(); } catch (ExecutionException exp) { System.err.println(exp); exp.printStackTrace(); } System.out.println("Bnd files converted in " + (System.currentTimeMillis()-t1) + " ms"); log.info("Bnd files converted in", (System.currentTimeMillis()-t1), "ms"); } /** * Reworks all bounds files of the given directory so that all boundaries * are applied with the information with which boundary they intersect.<br/> * The files are rewritten in the QUADTREE_DATA_FORMAT which is used in the * LocationHook. */ private void workoutBoundaryRelations() { List<String> boundsFileNames = BoundaryUtil.getBoundaryDirContent(this.outDir); for (String boundsFileName : boundsFileNames) { // start workers that rework the boundary files and add the // quadtree information addWorker(new QuadTreeWorker(this.outDir, boundsFileName)); } } @SuppressWarnings("unchecked") protected <V> Future<V> addWorker(Callable<V> worker) { if (threadPool == null) { // only one thread available for the preparer // so execute the task directly FutureTask<V> future = new FutureTask<>(worker); future.run(); return future; } Future<Object> task = threadPool.submit((Callable<Object>) worker); remainingTasks.add(task); return (Future<V>) task; } /** * Starts the preprocessing */ public void runPreprocessing() throws InterruptedException, ExecutionException { if (threadPool == null) { // there is no thread pool so run it in the same thread and wait for // its completion run(); } else { // start the preparer Future<Object> prepTask = threadPool.submit(this, new Object()); // first wait for the main preparer task to finish prepTask.get(); // then wait for all workers started by the preparer to finish while (true) { Future<Object> task = remainingTasks.poll(); if (task == null) { // no more remaining tasks // preparer has finished completely break; } // wait for the task to finish task.get(); } // stop thread pool threadPool.shutdown(); } } class QuadTreeWorker implements Callable<String> { private final String boundsDir; private final String boundsFilename; public QuadTreeWorker(String boundsDir, String boundsFilename) { this.boundsDir = boundsDir; this.boundsFilename = boundsFilename; } @Override public String call() throws Exception { log.info("Workout boundary relations in", boundsDir, boundsFilename); long t1 = System.currentTimeMillis(); BoundaryQuadTree bqt = BoundaryUtil.loadQuadTree(boundsDir, boundsFilename); long dt = System.currentTimeMillis() - t1; log.info("splitting", boundsFilename, "took", dt, "ms"); if (bqt != null){ BoundarySaver saver = new BoundarySaver(new File(boundsDir), BoundarySaver.QUADTREE_DATA_FORMAT); saver.setCreateEmptyFiles(false); saver.saveQuadTree(bqt, boundsFilename); saver.end(); } return boundsFilename; } } }