Skip to content

Commit 13f6b2e

Browse files
Imbrucedjiayuasu
authored andcommitted
[SEDONA-731] add osm nodes parser (#1920)
* SEDONA-731 add osm nodes parser * SEDONA-731 update docs * SEDONA-731 update docs
1 parent d930769 commit 13f6b2e

7 files changed

Lines changed: 118 additions & 2 deletions

File tree

docs/tutorial/sql.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ Since v1.7.1, Sedona supports loading OSM PBF file format as a DataFrame.
480480
```
481481

482482
OSM PBF files can contain nodes, ways, and relations. Currently Sedona support
483-
DenseNodes, Ways and Relations. When you load the data you get a DataFrame with the following schema.
483+
Nodes, DenseNodes, Ways and Relations. When you load the data you get a DataFrame with the following schema.
484484

485485
```
486486
root

spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/extractors/DenseNodeExtractor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ HashMap<String, String> parseTags(Osmformat.StringTable stringTable) {
8181
keyIndex = keyIndex + 2;
8282
}
8383

84+
keyIndex = keyIndex + 1;
85+
8486
return tags;
8587
}
8688
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.sedona.sql.datasources.osmpbf.extractors;
20+
21+
import java.util.HashMap;
22+
import org.apache.sedona.sql.datasources.osmpbf.build.Osmformat;
23+
import org.apache.sedona.sql.datasources.osmpbf.features.TagsResolver;
24+
import org.apache.sedona.sql.datasources.osmpbf.model.OsmNode;
25+
26+
public class NodeExtractor {
27+
28+
Osmformat.PrimitiveGroup primitiveGroup;
29+
Osmformat.PrimitiveBlock primitiveBlock;
30+
31+
public NodeExtractor(
32+
Osmformat.PrimitiveGroup primitiveGroup, Osmformat.PrimitiveBlock primitiveBlock) {
33+
this.primitiveGroup = primitiveGroup;
34+
this.primitiveBlock = primitiveBlock;
35+
}
36+
37+
public OsmNode extract(int idx, Osmformat.StringTable stringTable) {
38+
return parse(idx, stringTable);
39+
}
40+
41+
private OsmNode parse(int idx, Osmformat.StringTable stringTable) {
42+
Osmformat.Node node = primitiveGroup.getNodes(idx);
43+
44+
long id = node.getId();
45+
long latitude = node.getLat();
46+
long longitude = node.getLon();
47+
48+
long latOffset = primitiveBlock.getLatOffset();
49+
long lonOffset = primitiveBlock.getLonOffset();
50+
long granularity = primitiveBlock.getGranularity();
51+
52+
// https://wiki.openstreetmap.org/wiki/PBF_Format
53+
// latitude = .000000001 * (lat_offset + (granularity * lat))
54+
// longitude = .000000001 * (lon_offset + (granularity * lon))
55+
float lat = (float) (.000000001 * (latOffset + (latitude * granularity)));
56+
float lon = (float) (.000000001 * (lonOffset + (longitude * granularity)));
57+
58+
HashMap<String, String> tags =
59+
TagsResolver.resolveTags(node.getKeysCount(), node::getKeys, node::getVals, stringTable);
60+
61+
return new OsmNode(id, lat, lon, tags);
62+
}
63+
}

spark/common/src/main/java/org/apache/sedona/sql/datasources/osmpbf/iterators/BlobIterator.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.apache.sedona.sql.datasources.osmpbf.build.Fileformat.Blob;
2828
import org.apache.sedona.sql.datasources.osmpbf.build.Osmformat;
2929
import org.apache.sedona.sql.datasources.osmpbf.extractors.DenseNodeExtractor;
30+
import org.apache.sedona.sql.datasources.osmpbf.extractors.NodeExtractor;
3031
import org.apache.sedona.sql.datasources.osmpbf.extractors.RelationExtractor;
3132
import org.apache.sedona.sql.datasources.osmpbf.extractors.WaysExtractor;
3233
import org.apache.sedona.sql.datasources.osmpbf.model.OSMEntity;
@@ -68,7 +69,7 @@ public OSMEntity next() {
6869
}
6970

7071
if (!currentPrimitiveGroup.getNodesList().isEmpty()) {
71-
return null;
72+
return extractNodePrimitiveGroup();
7273
}
7374

7475
if (!currentPrimitiveGroup.getWaysList().isEmpty()) {
@@ -86,6 +87,18 @@ public OSMEntity next() {
8687
return null;
8788
}
8889

90+
private OSMEntity extractNodePrimitiveGroup() {
91+
osmEntityIdx += 1;
92+
if (currentPrimitiveGroup.getNodesList().size() == osmEntityIdx) {
93+
nextEntity();
94+
}
95+
96+
Osmformat.StringTable stringTable = primitiveBlock.getStringtable();
97+
98+
return new NodeExtractor(currentPrimitiveGroup, primitiveBlock)
99+
.extract(osmEntityIdx, stringTable);
100+
}
101+
89102
public OSMEntity extractDenseNodePrimitiveGroup() {
90103
if (denseNodesIterator == null) {
91104
denseNodesIterator =
282 Bytes
Binary file not shown.
242 Bytes
Binary file not shown.

spark/common/src/test/scala/org/apache/sedona/sql/OsmReaderTest.scala

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ import org.testcontainers.containers.MinIOContainer
2525

2626
import java.io.FileInputStream
2727

28+
case class Node(id: Long, latitude: Double, longitude: Double, tags: Map[String, String])
29+
2830
class OsmReaderTest extends TestBaseScala with Matchers {
2931
val monacoPath: String = resourceFolder + "osmpbf/monaco-latest.osm.pbf"
32+
val densePath: String = resourceFolder + "osmpbf/dense.pbf"
33+
val nodesPath: String = resourceFolder + "osmpbf/nodes.pbf"
3034

3135
import sparkSession.implicits._
3236

@@ -44,6 +48,40 @@ class OsmReaderTest extends TestBaseScala with Matchers {
4448
assert(cnt > 0)
4549
}
4650

51+
it("should parse normal nodes") {
52+
sparkSession.read
53+
.format("osmpbf")
54+
.load(nodesPath)
55+
.select("id", "location.*", "tags")
56+
.selectExpr(
57+
"id",
58+
"ROUND(latitude, 2) AS latitude",
59+
"ROUND(longitude, 2) AS longitude",
60+
"tags")
61+
.as[Node]
62+
.collect() should contain theSameElementsAs Array(
63+
Node(1002, 48.86, 2.35, Map("amenity" -> "cafe", "name" -> "Cafe de Paris")),
64+
Node(1003, 30.12, 22.23, Map("amenity" -> "bakery", "name" -> "Delicious Pastries")),
65+
Node(1001, 52.52, 13.40, Map("amenity" -> "restaurant", "name" -> "Curry 36")))
66+
}
67+
68+
it("should parse dense nodes") {
69+
sparkSession.read
70+
.format("osmpbf")
71+
.load(densePath)
72+
.select("id", "location.*", "tags")
73+
.selectExpr(
74+
"id",
75+
"ROUND(latitude, 2) AS latitude",
76+
"ROUND(longitude, 2) AS longitude",
77+
"tags")
78+
.as[Node]
79+
.collect() should contain theSameElementsAs Array(
80+
Node(1002, 48.86, 2.35, Map("amenity" -> "cafe", "name" -> "Cafe de Paris")),
81+
Node(1003, 30.12, 22.23, Map("amenity" -> "bakery", "name" -> "Delicious Pastries")),
82+
Node(1001, 52.52, 13.40, Map("amenity" -> "restaurant", "name" -> "Curry 36")))
83+
}
84+
4785
it("should be able to read from osm file on s3") {
4886
val container = new MinIOContainer("minio/minio:latest")
4987

0 commit comments

Comments
 (0)