/*
* Copyright (C) 2012, Gerd Petermann
*
* 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.splitter.writer;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import uk.me.parabola.splitter.Area;
import uk.me.parabola.splitter.Element;
import uk.me.parabola.splitter.Node;
import uk.me.parabola.splitter.Relation;
import uk.me.parabola.splitter.Relation.Member;
import uk.me.parabola.splitter.Utils;
import uk.me.parabola.splitter.Way;
/**
* Implements the needed methods to write the result in the o5m format.
* The routines to are based on the osmconvert.c source from Markus Weber who allows
* to copy them for any o5m IO, thanks a lot for that.
*
* @author GerdP
*
*/
public class O5mMapWriter
extends AbstractOSMWriter
{
// O5M data set constants
private static final int NODE_DATASET = 0x10
;
private static final int WAY_DATASET = 0x11
;
private static final int REL_DATASET = 0x12
;
private static final int BBOX_DATASET = 0xdb
;
//private static final int TIMESTAMP_DATASET = 0xdc;
private static final int HEADER_DATASET = 0xe0
;
private static final int EOD_FLAG = 0xfe
;
private static final int RESET_FLAG = 0xff
;
private static final int STW__TAB_MAX =
15000; // this is defined in the o5m format
private static final int STW_HASH_TAB_MAX =
30011; // (preferably a prime number)
private static final int STW_TAB_STR_MAX =
250;// this is defined in the o5m format
private static final String[] REL_REF_TYPES =
{"0",
"1",
"2"};
private static final double FACTOR =
10000000;
private OutputStream os
;
private byte[][][] stw__tab
; // string table
private byte[] s1Bytes
;
private byte[] s2Bytes
;
// for delta calculations
private long lastNodeId
;
private long lastWayId
;
private long lastRelId
;
private long lastRef
[];
private int lastLon,lastLat
;
private int lastWrittenDatasetType =
0;
// index of last entered element in string table
private short stw__tabi=
0;
// has table; elements point to matching strings in stw__tab[];
// -1: no matching element;
private short[] stw__hashtab
;
// for to chaining of string table rows which match
// the same hash value; matching rows are chained in a loop;
// if there is only one row matching, it will point to itself;
private short[] stw__tabprev
;
private short[] stw__tabnext
;
// has value of this element as a link back to the hash table;
// a -1 element indicates that the string table entry is not used;
private short[] stw__tabhash
;
private byte[] numberConversionBuf
;
private final static Map<String,
byte[]> wellKnownTagKeys =
new HashMap<>(60, 0.25f
);
private final static Map<String,
byte[]> wellKnownTagVals =
new HashMap<>(20, 0.25f
);
static {
try {
for (String s :
Arrays.
asList(
"1",
"1outer",
"1inner",
"type",
// relation specific
// 50 most often used keys (taken from taginfo 2016-11-20)
"building",
"source",
"highway",
"addr:housenumber",
"addr:street",
"name",
"addr:city",
"addr:postcode",
"natural",
"source:date",
"addr:country",
"landuse",
"surface",
"created_by",
"power",
"tiger:cfcc",
"waterway",
"tiger:county",
"start_date",
"tiger:reviewed",
"wall",
"amenity",
"oneway",
"ref:bag",
"ref",
"attribution",
"tiger:name_base",
"building:levels",
"maxspeed",
"barrier",
"tiger:name_type",
"height",
"service",
"source:addr",
"tiger:tlid",
"tiger:source",
"lanes",
"access",
"addr:place",
"tiger:zip_left",
"tiger:upload_uuid",
"layer",
"tracktype",
"ele",
"tiger:separated",
"tiger:zip_right",
"yh:WIDTH",
"place",
"foot"
)) {
wellKnownTagKeys.
put(s, s.
getBytes("UTF-8"));
}
for (String s :
Arrays.
asList(
"yes",
"no",
"residential",
"garage",
"water",
"tower",
"footway",
"Bing",
"PGS",
"private",
"stream",
"service",
"house",
"unclassified",
"track",
"traffic_signals",
"restaurant",
"entrance"
)) {
wellKnownTagVals.
put(s, s.
getBytes("UTF-8"));
}
} catch (Exception e
) {
// should not happen
}
}
//private long countCollisions;
public O5mMapWriter
(Area bounds,
File outputDir,
int mapId,
int extra
) {
super(bounds, outputDir, mapId, extra
);
}
private void reset
() throws IOException{
os.
write(RESET_FLAG
);
resetVars
();
}
/** reset the delta values and string table */
private void resetVars
(){
lastNodeId =
0; lastWayId =
0; lastRelId =
0;
lastRef
[0] =
0; lastRef
[1] =
0;lastRef
[2] =
0;
lastLon =
0; lastLat =
0;
stw__tab =
new byte[2][STW__TAB_MAX
][];
stw_reset
();
}
public void initForWrite
() {
// has table; elements point to matching strings in stw__tab[];
// -1: no matching element;
stw__hashtab =
new short[STW_HASH_TAB_MAX
];
// for to chaining of string table rows which match
// the same hash value; matching rows are chained in a loop;
// if there is only one row matching, it will point to itself;
stw__tabprev =
new short[STW__TAB_MAX
];
stw__tabnext =
new short[STW__TAB_MAX
];
// has value of this element as a link back to the hash table;
// a -1 element indicates that the string table entry is not used;
stw__tabhash =
new short[STW__TAB_MAX
];
lastRef =
new long[3];
numberConversionBuf =
new byte[60];
resetVars
();
String filename =
String.
format(Locale.
ROOT,
"%08d.o5m", mapId
);
try {
os =
new BufferedOutputStream(new FileOutputStream(new File(outputDir, filename
)));
os.
write(RESET_FLAG
);
writeHeader
();
writeBBox
();
} catch (IOException e
) {
System.
out.
println("Could not open or write file header. Reason: " + e.
getMessage());
throw new RuntimeException(e
);
}
}
private void writeHeader
() throws IOException {
ByteArrayOutputStream stream =
new ByteArrayOutputStream();
byte[] id =
{'o',
'5',
'm',
'2'};
stream.
write(id
);
writeDataset
(HEADER_DATASET,stream
);
}
private void writeBBox
() throws IOException {
ByteArrayOutputStream stream =
new ByteArrayOutputStream();
writeSignedNum
((long)(Utils.
toDegrees(bounds.
getMinLong()) * FACTOR
), stream
);
writeSignedNum
((long)(Utils.
toDegrees(bounds.
getMinLat()) * FACTOR
), stream
);
writeSignedNum
((long)(Utils.
toDegrees(bounds.
getMaxLong()) * FACTOR
), stream
);
writeSignedNum
((long)(Utils.
toDegrees(bounds.
getMaxLat()) * FACTOR
), stream
);
writeDataset
(BBOX_DATASET,stream
);
}
private void writeDataset
(int fileType,
ByteArrayOutputStream stream
) throws IOException {
os.
write(fileType
);
writeUnsignedNum
(stream.
size(), os
);
stream.
writeTo(os
);
lastWrittenDatasetType = fileType
;
}
public void finishWrite
() {
try {
os.
write(EOD_FLAG
);
os.
close();
stw__hashtab =
null;
stw__tabprev =
null;
stw__tabnext =
null;
stw__tabhash =
null;
lastRef =
null;
numberConversionBuf =
null;
stw__tab =
null;
//System.out.println(mapId + " collisions=" + Utils.format(countCollisions));
} catch (IOException e
) {
System.
out.
println("Could not write end of file: " + e
);
}
}
@
Override
public void write
(Node node
) throws IOException {
if (lastWrittenDatasetType
!= NODE_DATASET
){
reset
();
}
ByteArrayOutputStream stream =
new ByteArrayOutputStream();
long delta = node.
getId() - lastNodeId
; lastNodeId = node.
getId();
writeSignedNum
(delta, stream
);
writeVersion
(node, stream
);
int o5Lon =
(int)(node.
getLon() * FACTOR
);
int o5Lat =
(int)(node.
getLat() * FACTOR
);
int deltaLon = o5Lon - lastLon
; lastLon = o5Lon
;
int deltaLat = o5Lat - lastLat
; lastLat = o5Lat
;
writeSignedNum
(deltaLon, stream
);
writeSignedNum
(deltaLat, stream
);
writeTags
(node, stream
);
writeDataset
(NODE_DATASET,stream
);
}
public void write
(Way way
) throws IOException {
if (lastWrittenDatasetType
!= WAY_DATASET
){
reset
();
}
ByteArrayOutputStream stream =
new ByteArrayOutputStream();
long delta = way.
getId() - lastWayId
; lastWayId = way.
getId();
writeSignedNum
(delta, stream
);
writeVersion
(way, stream
);
ByteArrayOutputStream refStream =
new ByteArrayOutputStream();
LongArrayList refs = way.
getRefs();
int numRefs = refs.
size();
for (int i =
0; i
< numRefs
; i++
){
long ref = refs.
getLong(i
);
delta = ref - lastRef
[0]; lastRef
[0] = ref
;
writeSignedNum
(delta, refStream
);
}
writeUnsignedNum
(refStream.
size(),stream
);
refStream.
writeTo(stream
);
writeTags
(way, stream
);
writeDataset
(WAY_DATASET,stream
);
}
public void write
(Relation rel
) throws IOException {
if (lastWrittenDatasetType
!= REL_DATASET
){
reset
();
}
ByteArrayOutputStream stream =
new ByteArrayOutputStream(256);
long delta = rel.
getId() - lastRelId
; lastRelId = rel.
getId();
writeSignedNum
(delta, stream
);
writeVersion
(rel, stream
);
ByteArrayOutputStream memStream =
new ByteArrayOutputStream(256);
for (Member mem: rel.
getMembers()){
writeRelRef
(mem, memStream
);
}
writeUnsignedNum
(memStream.
size(),stream
);
memStream.
writeTo(stream
);
writeTags
(rel, stream
);
writeDataset
(REL_DATASET,stream
);
}
private void writeRelRef
(Member mem,
ByteArrayOutputStream memStream
) throws IOException {
int refType =
0;
String type = mem.
getType();
if ("node".
equals(type
))
refType =
0;
else if ("way".
equals(type
))
refType =
1;
else if ("relation".
equals(type
))
refType =
2;
else {
assert (false); // Software bug: Unknown entity.
}
long delta = mem.
getRef() - lastRef
[refType
]; lastRef
[refType
] = mem.
getRef();
writeSignedNum
(delta, memStream
);
stw_write
(REL_REF_TYPES
[refType
] + mem.
getRole(),
null, memStream
);
}
private void writeVersion
(Element element,
OutputStream stream
) throws IOException {
if (versionMethod == REMOVE_VERSION
){
stream.
write(0x00
); // no version
return;
}
int version =
1;
if (versionMethod == KEEP_VERSION
)
version = element.
getVersion();
if (version
!=
0){
writeUnsignedNum
(version, stream
);
}
stream.
write(0x00
); // no author or time-stamp info
}
private void writeTags
(Element element,
OutputStream stream
) throws IOException {
if (!element.
hasTags())
return;
Iterator<Element.
Tag> it = element.
tagsIterator();
while (it.
hasNext()) {
Element.
Tag entry = it.
next();
stw_write
(entry.
key, entry.
value, stream
);
}
}
private void stw_write
(String s1,
String s2,
OutputStream stream
) throws IOException {
int hash
;
int ref
;
s1Bytes = wellKnownTagKeys.
get(s1
);
if (s1Bytes ==
null){
s1Bytes = s1.
getBytes("UTF-8");
}
if (s2
!=
null){
s2Bytes = wellKnownTagVals.
get(s2
);
if (s2Bytes ==
null){
s2Bytes= s2.
getBytes("UTF-8");
}
}
else
s2Bytes =
null;
// try to find a matching string (pair) in string table
{
int i
; // index in stw__tab[]
ref = -
1; // ref invalid (default)
hash = stw_hash
(s1,s2
);
if (hash
>=
0){
i = stw__hashtab
[hash
];
if(i
>=
0) // string (pair) presumably stored already
ref = stw__getref
(i
);
} // end string (pair) short enough for the string table
if(ref
>=
0) { // we found the string (pair) in the table
writeUnsignedNum
(ref, stream
); // write just the reference
return;
} // end we found the string (pair) in the table
// write string data
stream.
write(0x00
);
stream.
write(s1Bytes
);
stream.
write(0x00
);
if (s2Bytes
!=
null){
stream.
write(s2Bytes
);
stream.
write(0x00
);
}
if(hash
< 0){ // string (pair) too long,
// cannot be stored in string table
return;
}
} // end try to find a matching string (pair) in string table
// here: there is no matching string (pair) in the table
// free new element - if still being used
{
int h0
; // hash value of old element
h0 = stw__tabhash
[stw__tabi
];
if(h0
>=
0) { // new element in string table is still being used
// delete old element
if(stw__tabnext
[stw__tabi
] == stw__tabi
)
// self-chain, i.e., only this element
stw__hashtab
[h0
]= -
1; // invalidate link in hash table
else { // one or more other elements in chain
stw__hashtab
[h0
] = stw__tabnext
[stw__tabi
]; // just to ensure
// that hash entry does not point to deleted element
// now unchain deleted element
stw__tabprev
[stw__tabnext
[stw__tabi
]]= stw__tabprev
[stw__tabi
];
stw__tabnext
[stw__tabprev
[stw__tabi
]]= stw__tabnext
[stw__tabi
];
} // end one or more other elements in chain
} // end next element in string table is still being used
} // end free new element - if still being used
// enter new string table element data
{
int i
;
stw__tab
[0][stw__tabi
] = s1Bytes
;
stw__tab
[1][stw__tabi
] = s2Bytes
;
i = stw__hashtab
[hash
];
if(i
< 0) // no reference in hash table until now
stw__tabprev
[stw__tabi
] = stw__tabnext
[stw__tabi
] = stw__tabi
;
// self-link the new element;
else { // there is already a reference in hash table
// in-chain the new element
stw__tabnext
[stw__tabi
] =
(short) i
;
stw__tabprev
[stw__tabi
] = stw__tabprev
[i
];
stw__tabnext
[stw__tabprev
[stw__tabi
]] = stw__tabi
;
stw__tabprev
[i
] = stw__tabi
;
//countCollisions++;
}
stw__hashtab
[hash
] = stw__tabi
; // link the new element to hash table
stw__tabhash
[stw__tabi
] =
(short) hash
; // backlink to hash table element
// new element now in use; set index to oldest element
if (++stw__tabi
>= STW__TAB_MAX
) { // index overflow
stw__tabi=
0; // restart index
} // end index overflow
} // end enter new string table element data
}
int stw__getref
(final int stri
) {
int strie
; // index of last occurrence
int ref
;
strie= stri
;
int pos = stri
;
do{
// compare the string (pair) with the tab entry
byte[] tb1 = stw__tab
[0][pos
];
if (Arrays.
equals(tb1, s1Bytes
)){
// first string equal to first string in table
byte[] tb2 = stw__tab
[1][pos
];
if (Arrays.
equals(tb2, s2Bytes
)){
// second string equal to second string in table
ref = stw__tabi - pos
;
if (ref
<=
0)
ref += STW__TAB_MAX
;
return ref
;
}
}
pos = stw__tabnext
[pos
];
} while(pos
!=strie
);
return -
1;
}
void stw_reset
() {
// clear string table and string hash table;
// must be called before any other procedure of this module
// and may be called every time the string processing shall
// be restarted;
stw__tabi =
0;
Arrays.
fill(stw__tabhash,
(short)-
1);
Arrays.
fill(stw__hashtab,
(short)-
1);
}
/**
* get hash value of a string pair
* @param s2
* @param s1
* @return hash value in the range 0..(STW__TAB_MAX-1)
* or -1 if the strings are longer than STW_TAB_STR_MAX bytes in total
* @throws IOException
*/
private int stw_hash
(String s1,
String s2
) throws IOException{
int len = s1Bytes.
length;
if (s2Bytes
!=
null)
len += s2Bytes.
length;
if (len
> STW_TAB_STR_MAX
)
return -
1;
int hash = s1.
hashCode();
if (s2
!=
null)
hash ^= s2.
hashCode();
return Math.
abs(hash
% STW__TAB_MAX
);
}
private int writeUnsignedNum
(int number,
OutputStream stream
)throws IOException {
int num = number
;
int cntBytes =
0;
int part = num
& 0x7f
;
if (part == num
){ // just one byte
stream.
write(part
);
return 1;
}
do{
numberConversionBuf
[cntBytes++
] =
(byte)(part | 0x80
);
num
>>=
7;
part = num
& 0x7f
;
} while(part
!= num
);
numberConversionBuf
[cntBytes++
] =
(byte)(part
);
stream.
write(numberConversionBuf,
0,cntBytes
);
return cntBytes
;
}
private int writeSignedNum
(long num,
OutputStream stream
)throws IOException {
int cntBytes =
0;
// write a long as signed varying integer.
// return: bytes written;
long u
;
int part
;
if (num
< 0){
u = -num
;
u=
(u
<<1)-
1;
}
else{
u= num
<<1;
}
part =
(int)(u
& 0x7f
);
if(part == u
) { // just one byte
stream.
write(part
);
return 1;
}
do {
numberConversionBuf
[cntBytes++
] =
(byte)(part | 0x80
);
u
>>>=
7;
part =
(int)(u
& 0x7f
);
} while(part
!=u
);
numberConversionBuf
[cntBytes++
] =
(byte)(part
);
stream.
write(numberConversionBuf,
0,cntBytes
);
return cntBytes
;
}
}