1use std::{
2 collections::{ BTreeMap, HashMap },
3 convert::TryInto,
4 sync::{ Arc, RwLock },
5};
6use log::warn;
7use geo_types as gt;
8use geojson as gj;
9use osmpbfreader::objects::{ OsmId, OsmObj };
10use osm_xml;
11
12use crate::{
13 features::{Address, GeoTileProperties, GeoTilesDataStructure, TILE_SCALE},
14 operations::{
15 line_string_operations::{draw_line_string, line_string_feature_to_geo_tile},
16 point_operations::{draw_point, point_feature_to_geo_tile},
17 polygon_operations::{draw_polygon, polygon_feature_to_geo_tile},
18 },
19 openstreetmap,
20 osmtogeojson,
21 pbf_parser::HasCoordinates,
22};
23
24pub mod line_string_operations;
25pub mod point_operations;
26pub mod polygon_operations;
27
28pub fn to_tile_scale(unit: f64) -> i32 {
31 return (unit * TILE_SCALE).round() as i32
32}
33
34pub fn from_tile_scale(unit : i32) -> f64 {
38 return (unit as f64) / TILE_SCALE;
39}
40
41pub fn from_tile_scale_u8(unit : u8) -> f64 {
43 return (unit as f64) / TILE_SCALE;
44}
45
46pub fn property_to_option_string(props: &dyn GeoTileProperties, key: &str) -> Option<String> {
47 match props.fetch(key) {
48 Some(value) => Some(value.to_string()),
49 _ => None,
50 }
51}
52
53pub fn address_from_properties(props: &dyn GeoTileProperties) -> Option<Address> {
54 if props.has("addr:housenumber")
55 || props.has("addr:unit")
56 || props.has("addr:street")
57 || props.has("addr:postcode")
58 {
59 let house_number = match props.fetch("addr:housenumber") {
60 Some(value) => Some(String::from(value)),
61 _ => None,
62 };
63 let unit = match props.fetch("addr:unit") {
64 Some(value) => Some(String::from(value)),
65 _ => None,
66 };
67 let street = match props.fetch("addr:street") {
68 Some(value) => Some(String::from(value)),
69 _ => None,
70 };
71 let postal_code = match props.fetch("addr:postcode") {
72 Some(value) => Some(String::from(value)),
73 _ => None,
74 };
75 Some(Address {
76 house_number,
77 unit,
78 street,
79 postal_code,
80 })
81 } else {
82 None
83 }
84}
85
86pub fn get_geojson_file_by_lat_lon(
87 lat: f64,
88 lon: f64,
89 radius: f64,
90) -> Result<String, Box<dyn std::error::Error>> {
91 let left = lon - radius;
92 let bottom = lat - radius;
93 let right = lon + radius;
94 let top = lat + radius;
95 let osm_file = openstreetmap::download_osm_data_by_bbox(left, bottom, right, top)?;
96 let geojson_file = format!("{}.geojson", osm_file);
97 osmtogeojson::convert_osm_to_geojson(osm_file, geojson_file.clone())?;
98 Ok(geojson_file)
99}
100
101pub fn process_geojson(geojson: &gj::GeoJson) -> GeoTilesDataStructure {
102 let data_structure = GeoTilesDataStructure::new(RwLock::new(HashMap::new()));
103 process_geojson_with_data_structure(geojson, data_structure.clone());
104 data_structure
105}
106
107pub fn process_osm(osm_data: &osm_xml::OSM) -> GeoTilesDataStructure {
108 let data_structure = GeoTilesDataStructure::new(RwLock::new(HashMap::new()));
109 process_osm_with_data_structure(osm_data, data_structure.clone());
110 data_structure
111}
112
113pub fn process_pbf(pbf_data: &BTreeMap<OsmId, OsmObj>) -> GeoTilesDataStructure {
114 let data_structure = GeoTilesDataStructure::new(RwLock::new(HashMap::new()));
115 process_pbf_with_data_structure(pbf_data, data_structure.clone());
116 data_structure
117}
118
119pub fn process_geojson_with_data_structure(geojson: &gj::GeoJson, data_structure: GeoTilesDataStructure) {
120 match *geojson {
121 gj::GeoJson::FeatureCollection(ref ctn) => {
122 for feature in &ctn.features {
123 if feature.properties.is_some() && feature.geometry.is_some() {
125 process_feature(
126 feature.properties.as_ref().unwrap(),
127 &feature.geometry.as_ref().unwrap(),
128 data_structure.clone(),
129 )
130 } else {
131 warn!("Found feature from features without properties or geometry");
132 }
133 }
134 }
135 gj::GeoJson::Feature(ref feature) => {
136 if feature.properties.is_some() && feature.geometry.is_some() {
138 process_feature(
139 feature.properties.as_ref().unwrap(),
140 &feature.geometry.as_ref().unwrap(),
141 data_structure,
142 )
143 } else {
144 warn!("Found feature without properties or geometry");
145 }
146 }
147 gj::GeoJson::Geometry(_) => {
148 warn!("Found top-level geometry")
151 }
152 }
153}
154
155pub fn process_osm_with_data_structure(osm_data: &osm_xml::OSM, data_structure: GeoTilesDataStructure) {
156 for (_, node) in osm_data.nodes.iter() {
158 let point: gt::Point<f64> = (node.lat, node.lon).try_into().unwrap();
159 let geo_tile = Arc::new(point_feature_to_geo_tile(&node.tags, point));
160 draw_point(&point, geo_tile, data_structure.clone());
161 }
162 for (_, way) in osm_data.ways.iter() {
164 let mut coordinates: Vec<(f64, f64)> = Vec::new();
165 for node in way.nodes.iter() {
166 match osm_data.resolve_reference(&node) {
167 osm_xml::Reference::Node(n) => coordinates.push((n.lat, n.lon)),
168 osm_xml::Reference::Unresolved |
169 osm_xml::Reference::Way(_) |
170 osm_xml::Reference::Relation(_) => {
171 warn!("Found a non-node as part of way {}'s node list: {:?}", way.id, node);
172 }
173 }
174 }
175 if way.is_polygon() { let poly: gt::Polygon<f64> = gt::Polygon::new(coordinates.into(), vec![]);
177 let geo_tile = Arc::new(polygon_feature_to_geo_tile(&way.tags, poly.clone()));
178 draw_polygon(&poly, geo_tile, data_structure.clone());
179 } else { let line_string: gt::LineString<f64> = coordinates.into();
181 let geo_tile = Arc::new(line_string_feature_to_geo_tile(&way.tags, line_string));
182 draw_line_string(geo_tile, data_structure.clone());
183 }
184 }
185 }
188
189pub fn process_pbf_with_data_structure(pbf_data: &BTreeMap<OsmId, OsmObj>, data_structure: GeoTilesDataStructure) {
190 for obj in pbf_data.values() {
191 let mut tags = obj.tags().clone();
192 tags.insert("id".to_string(), obj.id().inner_id().to_string());
193 match obj {
194 OsmObj::Node(obj) => {
195 let point: gt::Point<f64> = (obj.lat(), obj.lon()).try_into().unwrap();
196 let geo_tile = Arc::new(point_feature_to_geo_tile(&tags, point));
197 draw_point(&point, geo_tile, data_structure.clone());
198 }
199 OsmObj::Way(obj) => {
200 let coordinates = obj.get_coordinates(&pbf_data);
201 if obj.is_open() { let line_string: gt::LineString<f64> = coordinates.into();
203 let geo_tile = Arc::new(line_string_feature_to_geo_tile(&tags, line_string));
204 draw_line_string(geo_tile, data_structure.clone());
205 } else { let poly: gt::Polygon<f64> = gt::Polygon::new(coordinates.into(), vec![]);
207 let geo_tile = Arc::new(polygon_feature_to_geo_tile(&tags, poly.clone()));
208 draw_polygon(&poly, geo_tile, data_structure.clone());
209 }
210 }
211 OsmObj::Relation(_obj) => {
212 }
215 }
216 }
217}
218
219fn process_feature(
220 properties: &dyn GeoTileProperties,
221 geometry: &gj::Geometry,
222 data_structure: GeoTilesDataStructure,
223) {
224 match geometry.value {
225 gj::Value::Polygon(_) => {
226 let poly: gt::Polygon<f64> =
227 TryInto::<gt::Polygon<f64>>::try_into(geometry.value.clone()).unwrap();
228 let geo_tile = Arc::new(polygon_feature_to_geo_tile(properties, poly.clone()));
229 draw_polygon(&poly, geo_tile, data_structure);
230 }
231 gj::Value::MultiPolygon(_) => {
232 let multi_polygon: gt::MultiPolygon<f64> =
233 TryInto::<gt::MultiPolygon<f64>>::try_into(geometry.value.clone()).unwrap();
234 for polygon in multi_polygon {
235 let poly: gt::Polygon<f64> =
236 TryInto::<gt::Polygon<f64>>::try_into(polygon).unwrap();
237 let geo_tile = Arc::new(polygon_feature_to_geo_tile(properties, poly.clone()));
238 draw_polygon(&poly, geo_tile, data_structure.clone());
239 }
240 }
241 gj::Value::GeometryCollection(ref gc) => {
242 for geom in gc {
243 process_feature(properties, geom, data_structure.clone())
244 }
245 }
246 gj::Value::LineString(_) => {
247 let line_string: gt::LineString<f64> =
248 TryInto::<gt::LineString<f64>>::try_into(geometry.value.clone()).unwrap();
249 let geo_tile = Arc::new(line_string_feature_to_geo_tile(properties, line_string));
250 draw_line_string(geo_tile, data_structure);
251 }
252 gj::Value::MultiLineString(_) => {
253 let multi_line_string: gt::MultiLineString<f64> =
254 TryInto::<gt::MultiLineString<f64>>::try_into(geometry.value.clone()).unwrap();
255 for line_string in multi_line_string {
256 let line_string: gt::LineString<f64> =
257 TryInto::<gt::LineString<f64>>::try_into(line_string).unwrap();
258 let geo_tile = Arc::new(line_string_feature_to_geo_tile(properties, line_string));
259 draw_line_string(geo_tile, data_structure.clone());
260 }
261 }
262 gj::Value::Point(_) => {
263 let point: gt::Point<f64> =
264 TryInto::<gt::Point<f64>>::try_into(geometry.value.clone()).unwrap();
265 let geo_tile = Arc::new(point_feature_to_geo_tile(properties, point));
266 draw_point(&point, geo_tile, data_structure);
267 }
268 gj::Value::MultiPoint(_) => {
269 let multi_point: gt::MultiPoint<f64> =
270 TryInto::<gt::MultiPoint<f64>>::try_into(geometry.value.clone()).unwrap();
271 for point in multi_point {
272 let point: gt::Point<f64> = TryInto::<gt::Point<f64>>::try_into(point).unwrap();
273 let geo_tile = Arc::new(point_feature_to_geo_tile(properties, point));
274 draw_point(&point, geo_tile, data_structure.clone());
275 }
276 }
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use crate::geojson_parser;
284
285 #[test]
286 fn test_parse_and_process_geojson_file() {
287 let geojson = geojson_parser::parse_geojson_file("resources/ottawa.xml.geojson");
288 process_geojson(&geojson);
289 }
290}