/************************************************************************** OSMemory library for OSM data processing. Copyright (C) 2014 Aleś Bułojčyk <alex73mail@gmail.com> This is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This software 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. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. **************************************************************************/ package org.alex73.osmemory; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Storage for all nodes, ways, relations. */ public class MemoryStorage { static final Pattern RE_OBJECT_CODE = Pattern.compile("([nwr])([0-9]+)"); // notes with tags sorted list protected final List<IOsmNode> nodes = new ArrayList<>(); // ways sorted list protected final List<IOsmWay> ways = new ArrayList<>(); // relations sorted list protected final List<IOsmRelation> relations = new ArrayList<>(); // simple nodes, i.e. without tags protected long[] simpleNodeIds = new long[4 * 1024 * 1024]; protected int[] simpleNodeLats = new int[4 * 1024 * 1024]; protected int[] simpleNodeLons = new int[4 * 1024 * 1024]; protected int simpleNodeCount; private final StringPack tagsPack = new StringPack(); private final StringPack relationRolesPack = new StringPack(); private final StringPack usersPack = new StringPack(); private long loadingStartTime, loadingFinishTime; public MemoryStorage() { loadingStartTime = System.currentTimeMillis(); } /** * Must be called after load data for optimization and some internal processing. */ void finishLoading() throws Exception { // check ID order long prev = 0; for (int i = 0; i < simpleNodeCount; i++) { long id = simpleNodeIds[i]; if (id <= prev) { throw new Exception("Nodes must be ordered by ID"); } prev = id; } prev = 0; for (int i = 0; i < nodes.size(); i++) { long id = nodes.get(i).getId(); if (id <= prev) { throw new Exception("Nodes must be ordered by ID"); } prev = id; } prev = 0; for (int i = 0; i < ways.size(); i++) { long id = ways.get(i).getId(); if (id <= prev) { throw new Exception("Ways must be ordered by ID"); } prev = id; } prev = 0; for (int i = 0; i < relations.size(); i++) { long id = relations.get(i).getId(); if (id <= prev) { throw new Exception("Relations must be ordered by ID"); } prev = id; } loadingFinishTime = System.currentTimeMillis(); } public StringPack getTagsPack() { return tagsPack; } public StringPack getRelationRolesPack() { return relationRolesPack; } public StringPack getUsersPack() { return usersPack; } private static <T extends IOsmObject> T getById(List<T> en, long id) { int i = binarySearch(en, id); return i < 0 ? null : en.get(i); } private static <T extends IOsmObject> void remove(List<T> en, long id) { int i = binarySearch(en, id); if (i >= 0) { en.remove(i); } } private static <T extends IOsmObject> int binarySearch(List<T> en, long id) { int low = 0; int high = en.size() - 1; while (low <= high) { int mid = (low + high) >>> 1; long midvalue = en.get(mid).getId(); if (midvalue < id) low = mid + 1; else if (midvalue > id) high = mid - 1; else return mid; } return -1; } private static <T extends IOsmObject> int getPositionForInsert(List<T> en, long id) { int low = 0; int high = en.size() - 1; while (low <= high) { int mid = (low + high) >>> 1; long midvalue = en.get(mid).getId(); if (midvalue < id) low = mid + 1; else if (midvalue > id) high = mid - 1; else throw new RuntimeException("Object already exist"); } if (low >= en.size()) { return en.size(); } long lowValue = en.get(low).getId(); if (lowValue > id) { return low; } else { return low - 1; } } private static <T extends IOsmObject> int getPositionForInsert(long[] ids, int idscount, long id) { int low = 0; int high = idscount - 1; while (low <= high) { int mid = (low + high) >>> 1; long midvalue = ids[mid]; if (midvalue < id) low = mid + 1; else if (midvalue > id) high = mid - 1; else throw new RuntimeException("Object already exist"); } if (low >= idscount) { return idscount; } long lowValue = ids[low]; if (lowValue > id) { return low; } else { return low - 1; } } public IOsmNode getNodeById(long id) { int pos = Arrays.binarySearch(simpleNodeIds, 0, simpleNodeCount, id); if (pos >= 0) { return new OsmSimpleNode(this, pos); } else { return getById(nodes, id); } } /** * Remove node. */ void removeNode(long id) { int pos = Arrays.binarySearch(simpleNodeIds, 0, simpleNodeCount, id); if (pos >= 0) { System.arraycopy(simpleNodeIds, pos + 1, simpleNodeIds, pos, simpleNodeCount - pos - 1); System.arraycopy(simpleNodeLats, pos + 1, simpleNodeLats, pos, simpleNodeCount - pos - 1); System.arraycopy(simpleNodeLons, pos + 1, simpleNodeLons, pos, simpleNodeCount - pos - 1); simpleNodeCount--; } else { remove(nodes, id); } } /** * Add or update node. */ void addSimpleNode(long id, int lat, int lon) { int pos = Arrays.binarySearch(simpleNodeIds, 0, simpleNodeCount, id); if (pos < 0) { removeNode(id); if (simpleNodeIds.length == simpleNodeCount) { // extend simple nodes simpleNodeIds = Arrays.copyOf(simpleNodeIds, simpleNodeIds.length + 4096); simpleNodeLats = Arrays.copyOf(simpleNodeLats, simpleNodeLats.length + 4096); simpleNodeLons = Arrays.copyOf(simpleNodeLons, simpleNodeLons.length + 4096); } pos = getPositionForInsert(simpleNodeIds, simpleNodeCount, id); System.arraycopy(simpleNodeIds, pos, simpleNodeIds, pos + 1, simpleNodeCount - pos); System.arraycopy(simpleNodeLats, pos, simpleNodeLats, pos + 1, simpleNodeCount - pos); System.arraycopy(simpleNodeLons, pos, simpleNodeLons, pos + 1, simpleNodeCount - pos); simpleNodeCount++; } simpleNodeIds[pos] = id; simpleNodeLats[pos] = lat; simpleNodeLons[pos] = lon; } /** * Add or update node. */ void addNode(IOsmNode n) { int posComplex = binarySearch(nodes, n.getId()); if (posComplex >= 0) { nodes.set(posComplex, n); } else { removeNode(n.getId()); int pos = getPositionForInsert(nodes, n.getId()); nodes.add(pos, n); } } public IOsmWay getWayById(long id) { return getById(ways, id); } /** * Remove way. */ void removeWay(long id) { remove(ways, id); } /** * Add or update way. */ void addWay(IOsmWay w) { int pos = binarySearch(ways, w.getId()); if (pos >= 0) { ways.set(pos, w); } else { pos = getPositionForInsert(ways, w.getId()); ways.add(pos, w); } } public IOsmRelation getRelationById(long id) { return getById(relations, id); } /** * Remove relation. */ void removeRelation(long id) { remove(relations, id); } /** * Add or update relation. */ void addRelation(IOsmRelation r) { int pos = binarySearch(relations, r.getId()); if (pos >= 0) { relations.set(pos, r); } else { pos = getPositionForInsert(relations, r.getId()); relations.add(pos, r); } } /** * Get object by code like n123, w456, r789. */ public IOsmObject getObject(String code) { Matcher m = RE_OBJECT_CODE.matcher(code.trim()); if (!m.matches()) { throw new RuntimeException("Няправільны фарматы code: " + code); } long idl = Long.parseLong(m.group(2)); switch (m.group(1)) { case "n": return getNodeById(idl); case "w": return getWayById(idl); case "r": return getRelationById(idl); default: throw new RuntimeException("Wrong code format: " + code); } } /** * Get object by Object ID. */ public IOsmObject getObject(IOsmObjectID objID) { switch (objID.getType()) { case IOsmObject.TYPE_NODE: return getNodeById(objID.getId()); case IOsmObject.TYPE_WAY: return getWayById(objID.getId()); case IOsmObject.TYPE_RELATION: return getRelationById(objID.getId()); default: throw new RuntimeException("Wrong type: " + objID.getType()); } } /** * Show some loading statistics. */ public void showStat() { DecimalFormat f = new DecimalFormat(",##0"); System.out.println("Loading time : " + f.format((loadingFinishTime - loadingStartTime)) + "ms"); System.out.println("Simple nodes count : " + f.format(simpleNodeCount)); System.out.println("Nodes count : " + f.format(nodes.size())); System.out.println("Ways count : " + f.format(ways.size())); System.out.println("Relations count : " + f.format(relations.size())); System.out.println("Tags count : " + f.format(tagsPack.tagCodes.size())); System.out.println("RelRoles count : " + f.format(relationRolesPack.tagCodes.size())); System.out.println("Users count : " + f.format(usersPack.tagCodes.size())); } /** * Process objects with specific tag. */ public void byTag(String tagName, Predicate<IOsmObject> predicate, Consumer<IOsmObject> consumer) { short tagKey = tagsPack.getTagCode(tagName); for (int i = 0; i < nodes.size(); i++) { IOsmNode n = nodes.get(i); if (n.hasTag(tagKey)) { if (predicate.test(n)) { consumer.accept(n); } } } for (int i = 0; i < ways.size(); i++) { IOsmWay w = ways.get(i); if (w.hasTag(tagKey)) { if (predicate.test(w)) { consumer.accept(w); } } } for (int i = 0; i < relations.size(); i++) { IOsmRelation r = relations.get(i); if (r.hasTag(tagKey)) { if (predicate.test(r)) { consumer.accept(r); } } } } /** * Process objects with specific tag. */ public void byTag(String tagName, Consumer<IOsmObject> consumer) { short tagKey = tagsPack.getTagCode(tagName); for (int i = 0; i < nodes.size(); i++) { IOsmNode n = nodes.get(i); if (n.hasTag(tagKey)) { consumer.accept(n); } } for (int i = 0; i < ways.size(); i++) { IOsmWay w = ways.get(i); if (w.hasTag(tagKey)) { consumer.accept(w); } } for (int i = 0; i < relations.size(); i++) { IOsmRelation r = relations.get(i); if (r.hasTag(tagKey)) { consumer.accept(r); } } } /** * Process all objects. */ public void all(Predicate<IOsmObject> predicate, Consumer<IOsmObject> consumer) { for (int i = 0; i < nodes.size(); i++) { IOsmNode n = nodes.get(i); if (predicate.test(n)) { consumer.accept(n); } } for (int i = 0; i < ways.size(); i++) { IOsmWay w = ways.get(i); if (predicate.test(w)) { consumer.accept(w); } } for (int i = 0; i < relations.size(); i++) { IOsmRelation r = relations.get(i); if (predicate.test(r)) { consumer.accept(r); } } } /** * Process all objects. */ public void all(Consumer<IOsmObject> consumer) { all(new Predicate<IOsmObject>() { @Override public boolean test(IOsmObject t) { return true; } }, consumer); } }