1use geo_types::{Coord, LineString, Point, Polygon};
2
3use osmic_core::error::OsmicResult;
4use osmic_core::geometry::Geometry;
5use osmic_core::NodeLocationStore;
6use osmic_osm::classify::classify;
7use osmic_osm::feature::Feature;
8use osmic_osm::tags::{TagStore, Tags};
9use osmic_osm::LayerSet;
10use osmic_tiles::TileGeneratorConfig;
11use tracing::info;
12
13use crate::dirty::DirtyTileSet;
14use crate::osc::{ChangeAction, OscChange, OscElement};
15use crate::store::FeatureStore;
16
17pub fn apply_changes(
20 changes: &[OscChange],
21 store: &FeatureStore,
22 node_store: &dyn NodeLocationStore,
23 tag_store: &TagStore,
24 layers: &LayerSet,
25 config: &TileGeneratorConfig,
26) -> OsmicResult<DirtyTileSet> {
27 let mut dirty = DirtyTileSet::new();
28 let mut created = 0u64;
29 let mut modified = 0u64;
30 let mut deleted = 0u64;
31 let mut skipped = 0u64;
32
33 for change in changes {
34 match change.action {
35 ChangeAction::Create | ChangeAction::Modify => {
36 if let Some(feature) = build_feature(&change.element, node_store, tag_store, layers)
37 {
38 let new_bbox = feature.bbox();
39
40 let old_bbox = store.upsert(feature.id, &new_bbox)?;
42
43 if let Some(old) = old_bbox {
45 dirty.mark_bbox(&old, config.min_zoom, config.max_zoom);
46 }
47 dirty.mark_bbox(&new_bbox, config.min_zoom, config.max_zoom);
48
49 if change.action == ChangeAction::Create {
50 created += 1;
51 } else {
52 modified += 1;
53 }
54 } else {
55 skipped += 1;
56 }
57 }
58 ChangeAction::Delete => {
59 let id = change.element.id();
60 if let Some(old_bbox) = store.delete(id)? {
61 dirty.mark_bbox(&old_bbox, config.min_zoom, config.max_zoom);
62 deleted += 1;
63 }
64 }
65 }
66 }
67
68 info!(
69 created,
70 modified,
71 deleted,
72 skipped,
73 dirty_tiles = dirty.len(),
74 "Changes applied"
75 );
76
77 Ok(dirty)
78}
79
80fn build_feature(
83 element: &OscElement,
84 node_store: &dyn NodeLocationStore,
85 tag_store: &TagStore,
86 layers: &LayerSet,
87) -> Option<Feature> {
88 match element {
89 OscElement::Node {
90 id, lon, lat, tags, ..
91 } => {
92 let interned_tags = intern_tags(tags, tag_store);
93 let kind = classify(&interned_tags, tag_store, layers)?;
94 Some(Feature {
95 id: *id,
96 kind,
97 geometry: Geometry::Point(Point::new(*lon, *lat)),
98 tags: interned_tags,
99 })
100 }
101 OscElement::Way {
102 id,
103 node_refs,
104 tags,
105 ..
106 } => {
107 let coords: Vec<Coord<f64>> = node_refs
109 .iter()
110 .filter_map(|&nid| {
111 let lonlat = node_store.get(nid)?;
112 Some(Coord {
113 x: lonlat.lon,
114 y: lonlat.lat,
115 })
116 })
117 .collect();
118
119 if coords.len() < 2 {
120 return None;
121 }
122
123 let interned_tags = intern_tags(tags, tag_store);
124 let kind = classify(&interned_tags, tag_store, layers)?;
125
126 let is_closed = coords.len() >= 4 && coords.first() == coords.last();
128 let mut geometry = if is_closed && kind.is_area() {
129 Geometry::Polygon(Polygon::new(LineString::new(coords), vec![]))
130 } else {
131 Geometry::Line(LineString::new(coords))
132 };
133 osmic_geo::orient::orient_geometry(&mut geometry);
134
135 Some(Feature {
136 id: *id,
137 kind,
138 geometry,
139 tags: interned_tags,
140 })
141 }
142 OscElement::Relation { .. } => {
143 None
147 }
148 }
149}
150
151fn intern_tags(tags: &[(String, String)], tag_store: &TagStore) -> Tags {
153 let mut interned = Tags::new();
154 for (k, v) in tags {
155 let key = tag_store.intern_key(k);
156 let val = tag_store.intern_value(v);
157 interned.push(key, val);
158 }
159 interned
160}