/*
* Copyright (C) 2006, 2011.
*
* 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.awt.geom.Area;
import java.awt.geom.Path2D;
import java.io.File;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.mkgmap.reader.osm.Tags;
import uk.me.parabola.mkgmap.reader.osm.Way;
import uk.me.parabola.util.GpxCreator;
import uk.me.parabola.util.Java2DConverter;
/**
* Compare two boundary files or two directories with boundary files.
* Write differences as gpx files.
*
* @author WanMil (initial version), rewritten for BoundaryQuadTree by GerdP
*
*/
public class BoundaryDiff {
private final String inputName1;
private final String inputName2;
public BoundaryDiff(String boundaryDirName1, String boundaryDirName2) {
this.inputName1 = boundaryDirName1;
this.inputName2 = boundaryDirName2;
}
/**
* Return list of file names
* @param dirName a directory or a zip file containing *.bnd files,
* or a single *.bnd file
* @return
*/
private static List<String> getBoundsFiles(String dirName) {
File dir = new File(dirName);
System.out.println(dirName);
if (dir.isFile() && dir.getName().endsWith(".bnd")) {
List<String> boundaryFiles = new ArrayList<>();
boundaryFiles.add(dir.getName());
return boundaryFiles;
}
return BoundaryUtil.getBoundaryDirContent(dirName);
}
/**
* Compare all files in one list with the files in another list.
* Optionally restrict the comparison to boundaries with the
* given tag/value combination.
* @param tag should be admin_level
* @param value any value appropriate for the tag
*/
public void compare(String tag, String value) {
List<String> b1 = getBoundsFiles(inputName1);
List<String> b2 = getBoundsFiles(inputName2);
if (b1.size() == 0 && b2.size() == 0)
return;
Collections.sort(b1);
Collections.sort(b2);
Queue<String> bounds1 = new LinkedList<>(b1);
Queue<String> bounds2 = new LinkedList<>(b2);
b1 = null;
b2 = null;
Area only1 = new Area();
Area only2 = new Area();
int bAll = bounds1.size() + bounds2.size();
long tProgress = System.currentTimeMillis();
while (bounds1.isEmpty() == false || bounds2.isEmpty() == false) {
String f1 = bounds1.peek();
String f2 = bounds2.peek();
if (f1 == null) {
only2.add(loadArea(inputName2, f2, tag, value));
bounds2.poll();
} else if (f2 == null) {
only1.add(loadArea(inputName1, f1, tag, value));
bounds1.poll();
} else {
int cmp = f1.compareTo(f2);
if (cmp == 0) {
Area a1 = loadArea(inputName1, f1, tag, value);
Area a2 = loadArea(inputName2, f2, tag, value);
if (a1.isEmpty() == false|| a2.isEmpty() == false){
Area o1 = new Area(a1);
o1.subtract(a2);
if (o1.isEmpty() == false)
only1.add(o1);
Area o2 = new Area(a2);
o2.subtract(a1);
if (o2.isEmpty() == false)
only2.add(o2);
}
bounds1.poll();
bounds2.poll();
} else if (cmp < 0) {
only1.add(loadArea(inputName1, f1, tag, value));
bounds1.poll();
} else {
only2.add(loadArea(inputName2, f2, tag, value));
bounds2.poll();
}
}
long tNow = System.currentTimeMillis();
if (tNow - tProgress >= 10*1000L) {
int bNow = bounds1.size()+ bounds2.size();
System.out.println(tag+"="+value+": "+(bAll-bNow)+"/"+bAll+" files - "+(bAll-bNow)*100/bAll+"%");
tProgress = tNow;
}
}
saveArea(only1, "removed", tag, value);
saveArea(only2, "new", tag, value);
}
/**
* Calculate the area that is covered by a given tag /value pair, e.g. admin_level=2
* @param dirName the name of a directory or *.zip file containing *.bnd files, or a single *.bnd file
* @param fileName the name of the *.bnd file that should be read
* @param tag the tag key
* @param value the tag value
* @return a new Area (which might be empty)
*/
private static Area loadArea(String dirName, String fileName, String tag, String value) {
String dir = dirName;
String bndFileName = fileName;
if (dir.endsWith(".bnd")){
File f = new File(dir);
if (f.isFile()){
dir = f.getParent();
bndFileName = f.getName();
}
if (dir == null)
dir = "."; // the local directory
}
BoundaryQuadTree bqt = BoundaryUtil.loadQuadTree(dir, bndFileName);
if (tag.equals("admin_level"))
return (bqt.getCoveredArea(Integer.valueOf(value)));
Map<String, Tags> bTags = bqt.getTagsMap();
Map<String, List<Area>> areas = bqt.getAreas();
Path2D.Double path = new Path2D.Double();
for (Entry<String, Tags> entry: bTags.entrySet()){
if (value.equals(entry.getValue().get(tag))){
List<Area> aList = areas.get(entry.getKey());
for (Area area : aList){
path.append(area, false);
}
}
}
Area a = new Area(path);
return a;
}
/**
* Create gpx file(s) for a single area.
* @param a the Area
* @param subDirName target sub-directory
* @param tagKey used to build the gpx file name
* @param tagValue used to build the gpx file name
*/
private static void saveArea(Area a, String subDirName, String tagKey, String tagValue) {
String gpxBasename = "gpx/diff/" + subDirName + "/"
+ tagKey + "=" + tagValue + "/";
List<List<Coord>> singlePolys = Java2DConverter.areaToShapes(a);
Collections.reverse(singlePolys);
int i = 0;
for (List<Coord> polyPart : singlePolys) {
String attr = Way.clockwise(polyPart) ? "o" : "i";
GpxCreator.createGpx(gpxBasename + i + "_" + attr, polyPart);
i++;
}
}
/**
* print usage info
*/
private static void printUsage(){
System.err.println("Usage:");
System.err
.println("java -cp mkgmap.jar uk.me.parabola.mkgmap.reader.osm.boundary.BoundaryDiff <boundsdir1> <boundsdir2> [<tag=value> [<tag=value>]]");
System.err.println(" <boundsdir1> ");
System.err
.println(" <boundsdir2>: defines two directories or zip files containing boundsfiles to be compared ");
System.err
.println(" <tag=value>: defines a tag/value combination for which the diff is created");
System.err
.println(" sample:");
System.err
.println(" java -cp mkgmap.jar uk.me.parabola.mkgmap.reader.osm.boundary.BoundaryDiff world_20120113.zip bounds admin_level=2");
System.exit(-1);
}
public static void main(final String[] args) {
if (args.length < 2)
printUsage();
File f1 = new File(args[0]);
if (f1.exists() == false){
System.err.println(args[0] + " does not exist");
printUsage();
}
File f2 = new File(args[1]);
if (f2.exists() == false){
System.err.println(args[1] + " does not exist");
printUsage();
}
List<Entry<String,String>> tags = new ArrayList<>();
if (args.length > 2) {
for (int i = 2; i < args.length; i++) {
final String[] parts = args[i].split(Pattern.quote("="));
tags.add(new AbstractMap.SimpleImmutableEntry<>(
parts[0], parts[1]));
}
} else {
for (int adminlevel = 2; adminlevel <= 11; adminlevel++) {
tags.add(new AbstractMap.SimpleImmutableEntry<>(
"admin_level", String.valueOf(adminlevel)));
}
}
int processors = Runtime.getRuntime().availableProcessors();
ExecutorService excSvc = Executors.newFixedThreadPool(processors);
ExecutorCompletionService<String> executor = new ExecutorCompletionService<>(excSvc);
for (final Entry<String, String> tag : tags) {
executor.submit(new Runnable() {
public void run() {
BoundaryDiff bd = new BoundaryDiff(args[0],args[1]);
bd.compare(tag.getKey(), tag.getValue());
}
}, tag.getKey() + "=" + tag.getValue());
}
for (int i = 0; i < tags.size(); i++) {
try {
String tag = executor.take().get();
System.out.println(tag + " finished.");
} catch (InterruptedException exp) {
exp.printStackTrace();
} catch (ExecutionException exp) {
exp.printStackTrace();
}
}
excSvc.shutdown();
}
}