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