Subversion Repositories mkgmap

Rev

Rev 3343 | View as "text/plain" | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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;
                }

        }

}