/**************************************************************************
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.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.xml.bind.JAXBContext;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import osm.xmldatatypes.Member;
import osm.xmldatatypes.Nd;
import osm.xmldatatypes.Node;
import osm.xmldatatypes.Osm;
import osm.xmldatatypes.OsmBasicChange;
import osm.xmldatatypes.OsmBasicType;
import osm.xmldatatypes.OsmChange;
import osm.xmldatatypes.Relation;
import osm.xmldatatypes.Tag;
import osm.xmldatatypes.Way;
/**
* Driver for OSM XML read. Format described at the http://wiki.openstreetmap.org/wiki/OSM_XML.
*/
public class XMLDriver {
static JAXBContext CONTEXT;
static {
try {
CONTEXT = JAXBContext.newInstance(OsmChange.class, Osm.class);
} catch (Exception ex) {
throw new ExceptionInInitializerError(ex);
}
}
final XMLReader handler;
protected long id;
protected double lat, lon;
protected String user;
protected List<Tag> tags = new ArrayList<>();
protected List<Long> nds = new ArrayList<>();
protected List<Member> members = new ArrayList<>();
public XMLDriver(XMLReader handler) {
this.handler = handler;
}
public void read(File file) throws Exception {
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {
parser.parse(in, new DefaultHandler() {
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
switch (qName) {
case "node":
id = Long.parseLong(attributes.getValue("id"));
lat = Double.parseDouble(attributes.getValue("lat"));
lon = Double.parseDouble(attributes.getValue("lon"));
user = attributes.getValue("user");
tags.clear();
break;
case "way":
id = Long.parseLong(attributes.getValue("id"));
user = attributes.getValue("user");
tags.clear();
nds.clear();
break;
case "nd":
nds.add(Long.parseLong(attributes.getValue("ref")));
break;
case "relation":
id = Long.parseLong(attributes.getValue("id"));
user = attributes.getValue("user");
tags.clear();
members.clear();
break;
case "member":
Member m = new Member();
m.setType(attributes.getValue("type"));
m.setRef(Long.parseLong(attributes.getValue("id")));
m.setRole(attributes.getValue("role"));
members.add(m);
break;
case "tag":
Tag t = new Tag();
t.setK(attributes.getValue("k"));
t.setV(attributes.getValue("v"));
tags.add(t);
break;
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
switch (qName) {
case "node":
handler.createNode(XMLDriver.this, id, lat, lon, user);
break;
case "way":
handler.createWay(XMLDriver.this, id, nds, user);
break;
case "relation":
handler.createRelation(XMLDriver.this, id, members, user);
break;
}
}
});
}
}
public void applyOsmChange(InputStream data, IApplyChangeCallback callback) throws Exception {
OsmChange changes = (OsmChange) CONTEXT.createUnmarshaller().unmarshal(data);
applyBasicChanges(XMLReader.UPDATE_MODE.CREATE, changes.getCreate(), callback);
applyBasicChanges(XMLReader.UPDATE_MODE.MODIFY, changes.getModify(), callback);
applyBasicChanges(XMLReader.UPDATE_MODE.DELETE, changes.getDelete(), callback);
}
static Map<String, String> tags(OsmBasicType obj) {
Map<String, String> r = new TreeMap<>();
for (Tag t : obj.getTag()) {
r.put(t.getK(), t.getV());
}
return r;
}
static long[] nodes(List<Nd> nds) {
long[] r = new long[nds.size()];
for (int i = 0; i < r.length; i++) {
r[i] = nds.get(i).getRef();
}
return r;
}
static long[] memberIds(List<Member> members) {
long[] r = new long[members.size()];
for (int i = 0; i < r.length; i++) {
r[i] = members.get(i).getRef();
}
return r;
}
static byte[] memberTypes(List<Member> members) {
byte[] r = new byte[members.size()];
for (int i = 0; i < r.length; i++) {
switch (members.get(i).getType()) {
case "node":
r[i] = IOsmObject.TYPE_NODE;
break;
case "way":
r[i] = IOsmObject.TYPE_WAY;
break;
case "relation":
r[i] = IOsmObject.TYPE_RELATION;
break;
default:
throw new RuntimeException("Unknown member type: " + members.get(i).getType());
}
}
return r;
}
static String[] memberRoles(List<Member> members) {
String[] r = new String[members.size()];
for (int i = 0; i < r.length; i++) {
r[i] = members.get(i).getRole();
}
return r;
}
void applyBasicChanges(XMLReader.UPDATE_MODE mode, List<OsmBasicChange> changes,
IApplyChangeCallback callback) {
for (OsmBasicChange c : changes) {
for (Node n : c.getNode()) {
callback.beforeUpdateNode(mode, n);
handler.updateNode(mode, n.getId(), n.getLat(), n.getLon(), tags(n), n.getUser());
callback.afterUpdateNode(mode, n);
}
for (Way w : c.getWay()) {
callback.beforeUpdateWay(mode, w);
handler.updateWay(mode, w.getId(), nodes(w.getNd()), tags(w), w.getUser());
callback.afterUpdateWay(mode, w);
}
for (Relation r : c.getRelation()) {
callback.beforeUpdateRelation(mode, r);
handler.updateRelation(mode, r.getId(), memberIds(r.getMember()), memberTypes(r.getMember()),
memberRoles(r.getMember()), tags(r), r.getUser());
callback.afterUpdateRelation(mode, r);
}
}
}
/**
* Application can use call back for each update. For define area of updates, for example.
*/
public interface IApplyChangeCallback {
void beforeUpdateNode(XMLReader.UPDATE_MODE mode, Node node);
void afterUpdateNode(XMLReader.UPDATE_MODE mode, Node node);
void beforeUpdateWay(XMLReader.UPDATE_MODE mode, Way way);
void afterUpdateWay(XMLReader.UPDATE_MODE mode, Way way);
void beforeUpdateRelation(XMLReader.UPDATE_MODE mode, Relation relation);
void afterUpdateRelation(XMLReader.UPDATE_MODE mode, Relation relation);
}
}