/*******************************************************************************
* Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com)
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v3
* which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt
******************************************************************************/
package com.opendoorlogistics.components.geocode.postcodes.impl;
import java.io.File;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.mapdb.DB;
import org.mapdb.DBMaker;
import com.opendoorlogistics.api.components.ComponentExecutionApi;
import com.opendoorlogistics.components.geocode.Countries;
import com.opendoorlogistics.components.geocode.Countries.Country;
import com.opendoorlogistics.core.utils.LargeList;
import com.opendoorlogistics.core.utils.Version;
import com.opendoorlogistics.core.utils.strings.Strings;
final public class PCGeocodeFile {
private final DB db;
private final String countryCode;
private final CountryConfigs.CountryProcessor processor;
private final Version version;
public interface ParseLevelDetailsCB{
void parseLevel(String country, String code, int level, long postcodeCount, String examples);
}
// private static final boolean DEBUG_PRINT_KEYS = false;
public PCGeocodeFile(File file) {
db = DBMaker.newFileDB(file).readOnly().closeOnJvmShutdown().transactionDisable().make();
// load the version
version = new Version(db.getAtomicString(PCConstants.DBNAME_VERSION).get());
// load the country code
countryCode = db.getAtomicString(PCConstants.DBNAME_COUNTRYCODE).get();
processor = CountryConfigs.getProcessor(countryCode);
if (processor == null) {
throw new RuntimeException("Cannot find country processor for country " + countryCode);
}
// if(DEBUG_PRINT_KEYS){
// Map<String,byte [] > pcMap = getPcMap();
// for(String key : pcMap.keySet()){
// System.out.println(key);
// }
// }
}
public void close() {
db.close();
}
public String getDescription() {
final StringBuilder builder = new StringBuilder();
Country country = getCountry();
if (country != null) {
builder.append("Country: " + country.getName());
}
if (builder.length() > 0) {
builder.append(" ");
}
builder.append("Code: " + countryCode.toUpperCase());
builder.append(System.lineSeparator());
parseLevels(new ParseLevelDetailsCB() {
@Override
public void parseLevel(String country, String code, int level, long postcodeCount, String examples) {
builder.append("Postcode level " + level + " has " + NumberFormat.getIntegerInstance().format(postcodeCount) + " postcodes");
builder.append(", examples (");
builder.append(examples);
builder.append(")");
builder.append(System.lineSeparator());
}
});
// for (Map.Entry<String, Object> entry : db.getAll().entrySet()) {
//
// if (entry.getKey().startsWith(PCConstants.DBNAME_PCS)) {
// Map<String, byte[]> map = (Map<String, byte[]>) entry.getValue();
// Integer level = Integer.parseInt(entry.getKey().substring(PCConstants.DBNAME_PCS.length()));
// builder.append("Postcode level " + level + " has " + NumberFormat.getIntegerInstance().format(map.size()) + " postcodes");
//
// int exampleCount=0;
// builder.append(", examples (");
// for(String s : map.keySet()){
// if(exampleCount>0){
// builder.append(", ");
// }
// builder.append(s.toUpperCase());
// exampleCount++;
// if(exampleCount==3){
// break;
// }
// }
// builder.append(")");
// builder.append(System.lineSeparator());
// }
// }
return builder.toString();
}
/**
* Parse the levels getting details of each one
* @param cb
*/
public void parseLevels(ParseLevelDetailsCB cb){
Country country = getCountry();
for (Map.Entry<String, Object> entry : db.getAll().entrySet()) {
if (entry.getKey().startsWith(PCConstants.DBNAME_PCS)) {
Map<String, byte[]> map = (Map<String, byte[]>) entry.getValue();
Integer level = Integer.parseInt(entry.getKey().substring(PCConstants.DBNAME_PCS.length()));
int size = map.size();
// builder.append("Postcode level " + level + " has " + NumberFormat.getIntegerInstance().format(map.size()) + " postcodes");
int exampleCount=0;
StringBuilder builder = new StringBuilder();
for(String s : map.keySet()){
if(exampleCount>0){
builder.append(", ");
}
builder.append(s.toUpperCase());
exampleCount++;
if(exampleCount==3){
break;
}
}
cb.parseLevel(country.getName(), country.getTwoDigitCode().toUpperCase(), level, size, builder.toString());
}
}
}
public Country getCountry() {
Country country = Countries.findBy2DigitCode(countryCode);
return country;
}
public static class PCFindResult{
private final List<PCRecord> list;
private final PCFindResultType type;
private final int level;
public PCFindResult(List<PCRecord> list, PCFindResultType type, int level) {
super();
this.list = list;
this.type = type;
this.level = level;
}
public List<PCRecord> getList() {
return list;
}
public PCFindResultType getType() {
return type;
}
public int getLevel(){
return level;
}
}
public static enum PCFindResultType{
FOUND,
INVALID_FORMAT,
NO_MATCHES
}
/**
* Find the postcode record
*
* @param isoCountryCode
* @param postcode
* @return
*/
public PCFindResult find(String postcode) {
// try splitting input
postcode = processor.standardisePostcode(postcode);
List<String> split = processor.splitByLevels(postcode, true);
assert split == null || split.size() == processor.nbLevels();
if(split == null){
return new PCFindResult(new ArrayList<PCRecord>() , PCFindResultType.INVALID_FORMAT,-1);
}
// try highest (most accurate) resolution first
for (int level = processor.nbLevels() - 1; level >= 0; level--) {
Map<String, byte[]> pcMap = getPCMap(level);
Map<Integer, String> intToStr = db.get(PCConstants.DBNAME_INT2ST);
// get the binary record. try full postcode first (in case user has entered a partial postcode e.g. GL51)
String cleanedPC = Strings.std(postcode);
byte[] bytes = pcMap.get(cleanedPC);
// then try the country-specific split (in case user has entered a corrupt or unknown end of postcode, e.g. GL51 XXX)
if (bytes == null && split != null && level < processor.nbLevels() - 1 && split.get(level) != null) {
cleanedPC = Strings.std(split.get(level));
bytes = pcMap.get(cleanedPC);
}
if (bytes == null) {
continue;
}
// deserialise
List<PCRecord> ret = PCSerialiser.multiDeserialise(bytes, intToStr);
return new PCFindResult(ret, PCFindResultType.FOUND, level);
}
return new PCFindResult(new ArrayList<PCRecord>() , PCFindResultType.NO_MATCHES,-1);
}
private Map<String, byte[]> getPCMap(int level) {
Map<String, byte[]> pcMap = db.get(PCConstants.DBNAME_PCS + Integer.toString(level));
return pcMap;
}
public List<PCRecord> getPostcodes(int level, ComponentExecutionApi reporter) {
Map<String, byte[]> pcMap = getPCMap(level);
LargeList<PCRecord> ret = new LargeList<>(pcMap.size());
int nbParsed = 0;
if (pcMap != null) {
Map<Integer, String> intToStr = db.get(PCConstants.DBNAME_INT2ST);
for (byte[] bytes : pcMap.values()) {
if (reporter.isCancelled() || reporter.isFinishNow()) {
return ret;
}
ret.addAll(PCSerialiser.multiDeserialise(bytes, intToStr));
if (nbParsed % 10000 == 0) {
reporter.postStatusMessage("Loaded " + nbParsed + " postcodes.");
}
nbParsed++;
}
}
return ret;
}
// public PCRecord findFirst(String postcode) {
// List<PCRecord> list = find(postcode);
// if (list.size() > 0) {
// return list.get(0);
// }
// return null;
// }
//
// public String getFindFirstSummary(String postcode) {
// PCRecord rec = findFirst(postcode);
// if (rec == null) {
// return "Postcode \"" + postcode + "\" did not match to anything.";
// } else {
// return "Postcode \"" + postcode + "\" matched: " + rec.toString();
// }
// }
public static void main(String[] args) {
//File file = new File("C:\\Users\\Phil\\Dropbox\\Personal\\Business\\Data\\output\\pt.gdf");
for(File file : new File("C:\\Users\\Phil\\Dropbox\\Personal\\Business\\Data\\output\\").listFiles()){
if(file.getAbsolutePath().toLowerCase().endsWith(".gdf")){
PCGeocodeFile pcf = new PCGeocodeFile(file);
System.out.println(pcf.getDescription());
System.out.println();
}
}
}
}