1use super::*;
2
3impl MapState {
4 pub fn pick(&self, query: PickQuery, options: PickOptions) -> PickResult {
6 let (query_coord, _ray) = self.resolve_pick_query(&query);
7 let mut result = PickResult {
8 hits: Vec::new(),
9 query_coord,
10 projection: Some(self.camera.projection()),
11 };
12 if options.include_terrain_surface {
13 if let Some(coord) = &query_coord {
14 let elevation = self.terrain.elevation_at(coord);
15 result
16 .hits
17 .push(PickHit::terrain_surface(*coord, elevation));
18 }
19 }
20 if let Some(coord) = &query_coord {
21 let tolerance_meters = if options.tolerance_meters > 0.0 {
22 options.tolerance_meters
23 } else {
24 self.camera.meters_per_pixel() * 8.0
25 };
26 self.pick_features_at_geo(coord, &options, tolerance_meters, &mut result.hits);
27 }
28 result.hits.sort_by(|a, b| {
29 a.layer_priority
30 .cmp(&b.layer_priority)
31 .then_with(|| a.distance_meters.total_cmp(&b.distance_meters))
32 });
33 if options.limit > 0 && result.hits.len() > options.limit {
34 result.hits.truncate(options.limit);
35 }
36 result
37 }
38
39 #[inline]
44 pub fn pick_at_screen(&self, x: f64, y: f64, options: PickOptions) -> PickResult {
45 self.pick(PickQuery::screen(x, y), options)
46 }
47
48 #[inline]
53 pub fn pick_at_geo(&self, coord: GeoCoord, options: PickOptions) -> PickResult {
54 self.pick(PickQuery::geo(coord), options)
55 }
56
57 #[inline]
62 pub fn pick_along_ray(
63 &self,
64 origin: glam::DVec3,
65 direction: glam::DVec3,
66 options: PickOptions,
67 ) -> PickResult {
68 self.pick(PickQuery::ray(origin, direction), options)
69 }
70
71 pub fn query_rendered_features(
73 &self,
74 query: PickQuery,
75 options: QueryOptions,
76 ) -> Vec<QueriedFeature> {
77 let (query_coord, _ray) = self.resolve_pick_query(&query);
78 let Some(coord) = query_coord else {
79 return Vec::new();
80 };
81
82 let tolerance_meters = if options.tolerance_meters > 0.0 {
83 options.tolerance_meters
84 } else {
85 self.camera.meters_per_pixel() * 8.0
86 };
87
88 self.query_vector_features_at_geo(&coord, &options, tolerance_meters)
89 }
90
91 #[inline]
96 pub fn query_rendered_features_at_screen(
97 &self,
98 x: f64,
99 y: f64,
100 options: QueryOptions,
101 ) -> Vec<QueriedFeature> {
102 self.query_rendered_features(PickQuery::screen(x, y), options)
103 }
104
105 #[inline]
110 pub fn query_rendered_features_at_geo(
111 &self,
112 coord: GeoCoord,
113 options: QueryOptions,
114 ) -> Vec<QueriedFeature> {
115 self.query_rendered_features(PickQuery::geo(coord), options)
116 }
117
118 #[inline]
124 pub fn query_rendered_features_along_ray(
125 &self,
126 origin: glam::DVec3,
127 direction: glam::DVec3,
128 options: QueryOptions,
129 ) -> Vec<QueriedFeature> {
130 self.query_rendered_features(PickQuery::ray(origin, direction), options)
131 }
132
133 pub fn query_rendered_features_in_box(
156 &self,
157 x1: f64,
158 y1: f64,
159 x2: f64,
160 y2: f64,
161 options: QueryOptions,
162 ) -> Vec<QueriedFeature> {
163 let geo1 = self.camera.screen_to_geo(x1, y1);
165 let geo2 = self.camera.screen_to_geo(x2, y2);
166 let (Some(a), Some(b)) = (geo1, geo2) else {
167 return Vec::new();
168 };
169 self.query_vector_features_in_bbox(&a, &b, &options)
170 }
171
172 pub fn query_source_features(
174 &self,
175 source_id: &str,
176 source_layer: Option<&str>,
177 ) -> Vec<QueriedFeature> {
178 use crate::layer::LayerKind;
179 use crate::layers::VectorLayer;
180
181 let mut out = Vec::new();
182 let mut seen = HashSet::new();
183
184 for layer in self.layers.iter() {
185 if !layer.visible() || layer.kind() != LayerKind::Vector {
186 continue;
187 }
188
189 let Some(vector_layer) = layer.as_any().downcast_ref::<VectorLayer>() else {
190 continue;
191 };
192
193 let layer_source_id = vector_layer
194 .query_source_id
195 .as_deref()
196 .unwrap_or(layer.name());
197 if layer_source_id != source_id {
198 continue;
199 }
200
201 let layer_id = vector_layer
202 .query_layer_id
203 .as_deref()
204 .unwrap_or(layer.name());
205
206 let streamed_payloads = self.streamed_payload_view_for(layer_id);
207 if !streamed_payloads.is_empty() {
208 for entry in streamed_payloads.iter() {
209 let feature_source_layer =
210 entry.source_layer(vector_layer.query_source_layer.as_deref());
211 if let Some(requested_source_layer) = source_layer {
212 if feature_source_layer != Some(requested_source_layer) {
213 continue;
214 }
215 }
216
217 let seen_key = (
218 feature_source_layer.map(str::to_owned),
219 format!("{:?}", entry.feature.geometry),
220 );
221 if !seen.insert(seen_key) {
222 continue;
223 }
224
225 out.push(QueriedFeature {
226 layer_id: None,
227 source_id: Some(source_id.to_owned()),
228 source_layer: feature_source_layer.map(str::to_owned),
229 source_tile: entry.source_tile(),
230 feature_id: entry.feature.feature_id.clone(),
231 feature_index: entry.feature.feature_index,
232 geometry: entry.feature.geometry.clone(),
233 properties: entry.feature.properties.clone(),
234 state: self
235 .feature_state
236 .get(&FeatureStateId::new(source_id, &entry.feature.feature_id))
237 .cloned()
238 .unwrap_or_default(),
239 distance_meters: 0.0,
240 from_symbol: false,
241 });
242 }
243 continue;
244 }
245
246 for (idx, feature) in vector_layer.features.features.iter().enumerate() {
247 let provenance = vector_layer
248 .feature_provenance
249 .get(idx)
250 .and_then(|entry| entry.as_ref());
251 let feature_source_layer = provenance
252 .and_then(|entry| entry.source_layer.as_deref())
253 .or(vector_layer.query_source_layer.as_deref());
254 if let Some(requested_source_layer) = source_layer {
255 if feature_source_layer != Some(requested_source_layer) {
256 continue;
257 }
258 }
259
260 let feature_id = feature_id_for_feature(feature, idx);
261 let seen_key = (
262 feature_source_layer.map(str::to_owned),
263 format!("{:?}", feature.geometry),
264 );
265 if !seen.insert(seen_key) {
266 continue;
267 }
268
269 out.push(QueriedFeature {
270 layer_id: None,
271 source_id: Some(source_id.to_owned()),
272 source_layer: feature_source_layer.map(str::to_owned),
273 source_tile: provenance.and_then(|entry| entry.source_tile),
274 feature_id: feature_id.clone(),
275 feature_index: idx,
276 geometry: feature.geometry.clone(),
277 properties: feature.properties.clone(),
278 state: self
279 .feature_state
280 .get(&FeatureStateId::new(source_id, &feature_id))
281 .cloned()
282 .unwrap_or_default(),
283 distance_meters: 0.0,
284 from_symbol: false,
285 });
286 }
287 }
288
289 out
290 }
291
292 fn resolve_pick_query(
294 &self,
295 query: &PickQuery,
296 ) -> (Option<GeoCoord>, Option<(glam::DVec3, glam::DVec3)>) {
297 match query {
298 PickQuery::Screen { x, y } => {
299 let coord = self.screen_to_geo(*x, *y);
300 let ray = Some(self.camera.screen_to_ray(*x, *y));
301 (coord, ray)
302 }
303 PickQuery::Geo { coord } => (Some(*coord), None),
304 PickQuery::Ray { origin, direction } => {
305 let coord = self.ray_to_geo(*origin, *direction);
306 (coord, Some((*origin, *direction)))
307 }
308 }
309 }
310
311 fn query_vector_features_at_geo(
312 &self,
313 coord: &GeoCoord,
314 options: &QueryOptions,
315 tolerance_meters: f64,
316 ) -> Vec<QueriedFeature> {
317 use crate::layer::LayerKind;
318 use crate::layers::VectorLayer;
319
320 let filter_layers: Option<HashSet<&str>> = if options.layers.is_empty() {
321 None
322 } else {
323 Some(options.layers.iter().map(String::as_str).collect())
324 };
325 let filter_sources: Option<HashSet<&str>> = if options.sources.is_empty() {
326 None
327 } else {
328 Some(options.sources.iter().map(String::as_str).collect())
329 };
330
331 let mut out = Vec::new();
332 let mut seen_streamed = HashSet::new();
333
334 for layer in self.layers.iter() {
335 if !layer.visible() || layer.kind() != LayerKind::Vector {
336 continue;
337 }
338
339 let layer_name = layer.name();
340 let Some(vector_layer) = layer.as_any().downcast_ref::<VectorLayer>() else {
341 continue;
342 };
343
344 let query_layer_id = vector_layer.query_layer_id.as_deref().unwrap_or(layer_name);
345 if let Some(ref filtered_layers) = filter_layers {
346 if !filtered_layers.contains(query_layer_id)
347 && !filtered_layers.contains(layer_name)
348 {
349 continue;
350 }
351 }
352
353 let source_id = vector_layer
354 .query_source_id
355 .as_deref()
356 .unwrap_or(layer_name);
357 if let Some(ref filtered_sources) = filter_sources {
358 if !filtered_sources.contains(source_id) {
359 continue;
360 }
361 }
362
363 if options.include_symbols
364 && vector_layer.style.render_mode == crate::layers::VectorRenderMode::Symbol
365 {
366 if let Some(symbol_payloads) =
367 self.streamed_symbol_query_payloads.get(query_layer_id)
368 {
369 let mut seen_symbols = HashSet::new();
370 for payload in symbol_payloads {
371 for symbol in &payload.symbols {
372 if let Some(distance_meters) =
373 symbol_hit_distance_at_geo(symbol, coord, self.camera.projection())
374 {
375 let seen_key = (
376 source_id.to_owned(),
377 symbol.source_layer.clone(),
378 symbol.feature_id.clone(),
379 );
380 if !seen_symbols.insert(seen_key) {
381 continue;
382 }
383
384 out.push(QueriedFeature {
385 layer_id: Some(query_layer_id.to_owned()),
386 source_id: Some(source_id.to_owned()),
387 source_layer: symbol
388 .source_layer
389 .clone()
390 .or_else(|| vector_layer.query_source_layer.clone()),
391 source_tile: payload.tile.or(symbol.source_tile),
392 feature_id: symbol.feature_id.clone(),
393 feature_index: symbol.feature_index,
394 geometry: crate::geometry::Geometry::Point(
395 crate::geometry::Point {
396 coord: symbol.anchor,
397 },
398 ),
399 properties: HashMap::new(),
400 state: self
401 .feature_state
402 .get(&FeatureStateId::new(source_id, &symbol.feature_id))
403 .cloned()
404 .unwrap_or_default(),
405 distance_meters,
406 from_symbol: true,
407 });
408 }
409 }
410 }
411 continue;
412 }
413 }
414
415 if !options.include_symbols
416 && vector_layer.style.render_mode == crate::layers::VectorRenderMode::Symbol
417 {
418 continue;
419 }
420
421 let streamed_payloads = self.streamed_payload_view_for(query_layer_id);
422 if !streamed_payloads.is_empty() {
423 for entry in streamed_payloads.iter() {
424 let feature_source_layer = entry
425 .source_layer(vector_layer.query_source_layer.as_deref())
426 .map(str::to_owned);
427 if let Some(distance_meters) =
428 geometry_hit_distance(&entry.feature.geometry, coord, tolerance_meters)
429 {
430 let seen_key = (
431 source_id.to_owned(),
432 feature_source_layer.clone(),
433 format!("{:?}", entry.feature.geometry),
434 );
435 if !seen_streamed.insert(seen_key) {
436 continue;
437 }
438
439 out.push(QueriedFeature {
440 layer_id: Some(query_layer_id.to_owned()),
441 source_id: Some(source_id.to_owned()),
442 source_layer: feature_source_layer,
443 source_tile: entry.source_tile(),
444 feature_id: entry.feature.feature_id.clone(),
445 feature_index: entry.feature.feature_index,
446 geometry: entry.feature.geometry.clone(),
447 properties: entry.feature.properties.clone(),
448 state: self
449 .feature_state
450 .get(&FeatureStateId::new(source_id, &entry.feature.feature_id))
451 .cloned()
452 .unwrap_or_default(),
453 distance_meters,
454 from_symbol: false,
455 });
456 }
457 }
458 continue;
459 }
460
461 for (idx, feature) in vector_layer.features.features.iter().enumerate() {
462 if let Some(distance_meters) =
463 geometry_hit_distance(&feature.geometry, coord, tolerance_meters)
464 {
465 let feature_id = feature_id_for_feature(feature, idx);
466 let provenance = vector_layer
467 .feature_provenance
468 .get(idx)
469 .and_then(|entry| entry.as_ref());
470 out.push(QueriedFeature {
471 layer_id: Some(query_layer_id.to_owned()),
472 source_id: Some(source_id.to_owned()),
473 source_layer: provenance
474 .and_then(|entry| entry.source_layer.clone())
475 .or_else(|| vector_layer.query_source_layer.clone()),
476 source_tile: provenance.and_then(|entry| entry.source_tile),
477 feature_id: feature_id.clone(),
478 feature_index: idx,
479 geometry: feature.geometry.clone(),
480 properties: feature.properties.clone(),
481 state: self
482 .feature_state
483 .get(&FeatureStateId::new(source_id, &feature_id))
484 .cloned()
485 .unwrap_or_default(),
486 distance_meters,
487 from_symbol: false,
488 });
489 }
490 }
491 }
492
493 out.sort_by(|a, b| a.distance_meters.total_cmp(&b.distance_meters));
494 out
495 }
496
497 fn query_vector_features_in_bbox(
501 &self,
502 a: &GeoCoord,
503 b: &GeoCoord,
504 options: &QueryOptions,
505 ) -> Vec<QueriedFeature> {
506 use crate::layer::LayerKind;
507 use crate::layers::VectorLayer;
508 use crate::query::{geometry_intersects_bbox, GeoBBox};
509
510 let bbox = GeoBBox::from_geo_coords(a, b);
511
512 let filter_layers: Option<HashSet<&str>> = if options.layers.is_empty() {
513 None
514 } else {
515 Some(options.layers.iter().map(String::as_str).collect())
516 };
517 let filter_sources: Option<HashSet<&str>> = if options.sources.is_empty() {
518 None
519 } else {
520 Some(options.sources.iter().map(String::as_str).collect())
521 };
522
523 let mut out = Vec::new();
524 let mut seen_streamed = HashSet::new();
525
526 for layer in self.layers.iter() {
527 if !layer.visible() || layer.kind() != LayerKind::Vector {
528 continue;
529 }
530
531 let layer_name = layer.name();
532 let Some(vector_layer) = layer.as_any().downcast_ref::<VectorLayer>() else {
533 continue;
534 };
535
536 let query_layer_id = vector_layer.query_layer_id.as_deref().unwrap_or(layer_name);
537 if let Some(ref filtered_layers) = filter_layers {
538 if !filtered_layers.contains(query_layer_id)
539 && !filtered_layers.contains(layer_name)
540 {
541 continue;
542 }
543 }
544
545 let source_id = vector_layer
546 .query_source_id
547 .as_deref()
548 .unwrap_or(layer_name);
549 if let Some(ref filtered_sources) = filter_sources {
550 if !filtered_sources.contains(source_id) {
551 continue;
552 }
553 }
554
555 if !options.include_symbols
557 && vector_layer.style.render_mode == crate::layers::VectorRenderMode::Symbol
558 {
559 continue;
560 }
561
562 let streamed_payloads = self.streamed_payload_view_for(query_layer_id);
564 if !streamed_payloads.is_empty() {
565 for entry in streamed_payloads.iter() {
566 if geometry_intersects_bbox(&entry.feature.geometry, &bbox) {
567 let feature_source_layer = entry
568 .source_layer(vector_layer.query_source_layer.as_deref())
569 .map(str::to_owned);
570 let seen_key = (
571 source_id.to_owned(),
572 feature_source_layer.clone(),
573 format!("{:?}", entry.feature.geometry),
574 );
575 if !seen_streamed.insert(seen_key) {
576 continue;
577 }
578
579 out.push(QueriedFeature {
580 layer_id: Some(query_layer_id.to_owned()),
581 source_id: Some(source_id.to_owned()),
582 source_layer: feature_source_layer,
583 source_tile: entry.source_tile(),
584 feature_id: entry.feature.feature_id.clone(),
585 feature_index: entry.feature.feature_index,
586 geometry: entry.feature.geometry.clone(),
587 properties: entry.feature.properties.clone(),
588 state: self
589 .feature_state
590 .get(&FeatureStateId::new(source_id, &entry.feature.feature_id))
591 .cloned()
592 .unwrap_or_default(),
593 distance_meters: 0.0,
594 from_symbol: false,
595 });
596 }
597 }
598 continue;
599 }
600
601 for (idx, feature) in vector_layer.features.features.iter().enumerate() {
603 if geometry_intersects_bbox(&feature.geometry, &bbox) {
604 let feature_id = feature_id_for_feature(feature, idx);
605 let provenance = vector_layer
606 .feature_provenance
607 .get(idx)
608 .and_then(|entry| entry.as_ref());
609 out.push(QueriedFeature {
610 layer_id: Some(query_layer_id.to_owned()),
611 source_id: Some(source_id.to_owned()),
612 source_layer: provenance
613 .and_then(|entry| entry.source_layer.clone())
614 .or_else(|| vector_layer.query_source_layer.clone()),
615 source_tile: provenance.and_then(|entry| entry.source_tile),
616 feature_id: feature_id.clone(),
617 feature_index: idx,
618 geometry: feature.geometry.clone(),
619 properties: feature.properties.clone(),
620 state: self
621 .feature_state
622 .get(&FeatureStateId::new(source_id, &feature_id))
623 .cloned()
624 .unwrap_or_default(),
625 distance_meters: 0.0,
626 from_symbol: false,
627 });
628 }
629 }
630 }
631
632 out
633 }
634
635 fn pick_features_at_geo(
638 &self,
639 coord: &GeoCoord,
640 options: &PickOptions,
641 tolerance_meters: f64,
642 hits: &mut Vec<PickHit>,
643 ) {
644 use crate::layer::LayerKind;
645 use crate::layers::{ModelLayer, VectorLayer};
646
647 let filter_layers: Option<HashSet<&str>> = if options.layers.is_empty() {
648 None
649 } else {
650 Some(options.layers.iter().map(String::as_str).collect())
651 };
652 let filter_sources: Option<HashSet<&str>> = if options.sources.is_empty() {
653 None
654 } else {
655 Some(options.sources.iter().map(String::as_str).collect())
656 };
657
658 let mut priority: usize = 0;
659
660 for layer in self.layers.iter() {
661 if !layer.visible() {
662 continue;
663 }
664
665 let layer_name = layer.name().to_owned();
666
667 if let Some(ref fl) = filter_layers {
668 if !fl.contains(layer_name.as_str()) {
669 continue;
670 }
671 }
672
673 match layer.kind() {
674 LayerKind::Model => {
675 if let Some(model_layer) = layer.as_any().downcast_ref::<ModelLayer>() {
676 for inst in &model_layer.instances {
677 if let Some(dist) = model_hit_distance(inst, coord, tolerance_meters) {
678 hits.push(PickHit {
679 category: HitCategory::Model,
680 provenance: HitProvenance::GeometricApproximation,
681 layer_id: Some(layer_name.clone()),
682 source_id: None,
683 source_layer: None,
684 source_tile: None,
685 feature_id: None,
686 feature_index: None,
687 geometry: None,
688 properties: Default::default(),
689 state: Default::default(),
690 distance_meters: dist,
691 hit_coord: Some(inst.position),
692 layer_priority: priority as u32,
693 from_symbol: false,
694 });
695 }
696 }
697 }
698 }
699 LayerKind::Vector => {
700 if let Some(vector_layer) = layer.as_any().downcast_ref::<VectorLayer>() {
701 let source_id = vector_layer
702 .query_source_id
703 .as_deref()
704 .unwrap_or(&layer_name);
705
706 if let Some(ref fs) = filter_sources {
707 if !fs.contains(source_id) {
708 continue;
709 }
710 }
711
712 let category =
713 vector_render_mode_to_hit_category(&vector_layer.style.render_mode);
714
715 let layer_id = vector_layer
716 .query_layer_id
717 .as_deref()
718 .unwrap_or(&layer_name);
719
720 if options.include_symbols
721 && vector_layer.style.render_mode
722 == crate::layers::VectorRenderMode::Symbol
723 {
724 let symbol_payloads = self.streamed_symbol_query_payloads_for(layer_id);
725 if !symbol_payloads.is_empty() {
726 let mut seen_symbols = HashSet::new();
727 for payload in symbol_payloads {
728 for symbol in &payload.symbols {
729 if let Some(dist) = symbol_hit_distance_at_geo(
730 symbol,
731 coord,
732 self.camera.projection(),
733 ) {
734 let seen_key = (
735 source_id.to_owned(),
736 symbol.source_layer.clone(),
737 symbol.feature_id.clone(),
738 );
739 if !seen_symbols.insert(seen_key) {
740 continue;
741 }
742
743 let state = self
744 .feature_state
745 .get(&FeatureStateId::new(
746 source_id,
747 &symbol.feature_id,
748 ))
749 .cloned()
750 .unwrap_or_default();
751
752 hits.push(PickHit {
753 category: HitCategory::Symbol,
754 provenance: HitProvenance::GeometricApproximation,
755 layer_id: Some(layer_name.clone()),
756 source_id: Some(source_id.to_owned()),
757 source_layer: symbol.source_layer.clone().or_else(
758 || vector_layer.query_source_layer.clone(),
759 ),
760 source_tile: payload.tile.or(symbol.source_tile),
761 feature_id: Some(symbol.feature_id.clone()),
762 feature_index: Some(symbol.feature_index),
763 geometry: Some(crate::geometry::Geometry::Point(
764 crate::geometry::Point {
765 coord: symbol.anchor,
766 },
767 )),
768 properties: HashMap::new(),
769 state,
770 distance_meters: dist,
771 hit_coord: Some(*coord),
772 layer_priority: priority as u32,
773 from_symbol: true,
774 });
775 }
776 }
777 }
778 priority += 1;
779 continue;
780 }
781 }
782
783 if !options.include_symbols
784 && vector_layer.style.render_mode
785 == crate::layers::VectorRenderMode::Symbol
786 {
787 priority += 1;
788 continue;
789 }
790
791 let streamed_payloads = self.streamed_payload_view_for(layer_id);
792 if !streamed_payloads.is_empty() {
793 for entry in streamed_payloads.iter() {
794 if let Some(dist) = geometry_hit_distance(
795 &entry.feature.geometry,
796 coord,
797 tolerance_meters,
798 ) {
799 let state = self
800 .feature_state
801 .get(&FeatureStateId::new(
802 source_id,
803 &entry.feature.feature_id,
804 ))
805 .cloned()
806 .unwrap_or_default();
807
808 hits.push(PickHit {
809 category,
810 provenance: HitProvenance::GeometricApproximation,
811 layer_id: Some(layer_name.clone()),
812 source_id: Some(source_id.to_owned()),
813 source_layer: entry
814 .source_layer(
815 vector_layer.query_source_layer.as_deref(),
816 )
817 .map(str::to_owned),
818 source_tile: entry.source_tile(),
819 feature_id: Some(entry.feature.feature_id.clone()),
820 feature_index: Some(entry.feature.feature_index),
821 geometry: Some(entry.feature.geometry.clone()),
822 properties: entry.feature.properties.clone(),
823 state,
824 distance_meters: dist,
825 hit_coord: Some(*coord),
826 layer_priority: priority as u32,
827 from_symbol: false,
828 });
829 }
830 }
831 priority += 1;
832 continue;
833 }
834
835 for (idx, feature) in vector_layer.features.features.iter().enumerate() {
836 if let Some(dist) =
837 geometry_hit_distance(&feature.geometry, coord, tolerance_meters)
838 {
839 let fid = feature_id_for_feature(feature, idx);
840 let state = self
841 .feature_state
842 .get(&FeatureStateId::new(source_id, &fid))
843 .cloned()
844 .unwrap_or_default();
845 let provenance = vector_layer
846 .feature_provenance
847 .get(idx)
848 .and_then(|entry| entry.as_ref());
849
850 hits.push(PickHit {
851 category,
852 provenance: HitProvenance::GeometricApproximation,
853 layer_id: Some(layer_name.clone()),
854 source_id: Some(source_id.to_owned()),
855 source_layer: provenance
856 .and_then(|entry| entry.source_layer.clone())
857 .or_else(|| vector_layer.query_source_layer.clone()),
858 source_tile: provenance.and_then(|entry| entry.source_tile),
859 feature_id: Some(fid),
860 feature_index: Some(idx),
861 geometry: Some(feature.geometry.clone()),
862 properties: feature.properties.clone(),
863 state,
864 distance_meters: dist,
865 hit_coord: Some(*coord),
866 layer_priority: priority as u32,
867 from_symbol: false,
868 });
869 }
870 }
871 }
872 }
873 LayerKind::Visualization => self.pick_visualization_layer(
874 layer.as_any(),
875 &layer_name,
876 coord,
877 tolerance_meters,
878 priority as u32,
879 hits,
880 ),
881 _ => {}
882 }
883
884 priority += 1;
885 }
886 }
887
888 fn pick_visualization_layer(
889 &self,
890 layer: &dyn std::any::Any,
891 layer_name: &str,
892 coord: &GeoCoord,
893 tolerance_meters: f64,
894 layer_priority: u32,
895 hits: &mut Vec<PickHit>,
896 ) {
897 if let Some(grid_layer) = layer.downcast_ref::<crate::visualization::GridScalarLayer>() {
898 if let Some((row, col)) = grid_layer.grid.cell_at_geo(coord) {
899 if let Some(value) = grid_layer.field.sample(row, col) {
900 let cell_coord = grid_layer.grid.cell_center(row, col).unwrap_or(*coord);
901 let mut props = HashMap::new();
902 props.insert("value".to_owned(), PropertyValue::Number(value as f64));
903 props.insert("row".to_owned(), PropertyValue::Number(row as f64));
904 props.insert("col".to_owned(), PropertyValue::Number(col as f64));
905 hits.push(PickHit {
906 category: HitCategory::Feature,
907 provenance: HitProvenance::GeometricApproximation,
908 layer_id: Some(layer_name.to_owned()),
909 source_id: None,
910 source_layer: None,
911 source_tile: None,
912 feature_id: Some(format!("{row}:{col}")),
913 feature_index: Some(row * grid_layer.grid.cols + col),
914 geometry: None,
915 properties: props,
916 state: Default::default(),
917 distance_meters: 0.0,
918 hit_coord: Some(cell_coord),
919 layer_priority,
920 from_symbol: false,
921 });
922 }
923 }
924 return;
925 }
926
927 if let Some(grid_layer) = layer.downcast_ref::<crate::visualization::GridExtrusionLayer>() {
928 if let Some((row, col)) = grid_layer.grid.cell_at_geo(coord) {
929 if let Some(value) = grid_layer.field.sample(row, col) {
930 let cell_coord = grid_layer.grid.cell_center(row, col).unwrap_or(*coord);
931 let mut props = HashMap::new();
932 props.insert("value".to_owned(), PropertyValue::Number(value as f64));
933 props.insert("row".to_owned(), PropertyValue::Number(row as f64));
934 props.insert("col".to_owned(), PropertyValue::Number(col as f64));
935 props.insert(
936 "height".to_owned(),
937 PropertyValue::Number(value as f64 * grid_layer.params.height_scale),
938 );
939 hits.push(PickHit {
940 category: HitCategory::Feature,
941 provenance: HitProvenance::GeometricApproximation,
942 layer_id: Some(layer_name.to_owned()),
943 source_id: None,
944 source_layer: None,
945 source_tile: None,
946 feature_id: Some(format!("{row}:{col}")),
947 feature_index: Some(row * grid_layer.grid.cols + col),
948 geometry: None,
949 properties: props,
950 state: Default::default(),
951 distance_meters: 0.0,
952 hit_coord: Some(cell_coord),
953 layer_priority,
954 from_symbol: false,
955 });
956 }
957 }
958 return;
959 }
960
961 if let Some(col_layer) = layer.downcast_ref::<crate::visualization::InstancedColumnLayer>()
962 {
963 for (idx, column) in col_layer.columns.columns.iter().enumerate() {
964 let dist = geo_distance_meters(coord, &column.position);
965 let radius = column.width * 0.5 + tolerance_meters;
966 if dist <= radius {
967 let mut props = HashMap::new();
968 props.insert(
969 "pick_id".to_owned(),
970 PropertyValue::Number(column.pick_id as f64),
971 );
972 props.insert("height".to_owned(), PropertyValue::Number(column.height));
973 props.insert("width".to_owned(), PropertyValue::Number(column.width));
974 hits.push(PickHit {
975 category: HitCategory::Feature,
976 provenance: HitProvenance::GeometricApproximation,
977 layer_id: Some(layer_name.to_owned()),
978 source_id: None,
979 source_layer: None,
980 source_tile: None,
981 feature_id: Some(format!("col:{}", column.pick_id)),
982 feature_index: Some(idx),
983 geometry: None,
984 properties: props,
985 state: Default::default(),
986 distance_meters: dist,
987 hit_coord: Some(column.position),
988 layer_priority,
989 from_symbol: false,
990 });
991 }
992 }
993 return;
994 }
995
996 if let Some(point_layer) = layer.downcast_ref::<crate::visualization::PointCloudLayer>() {
997 for (idx, point) in point_layer.points.points.iter().enumerate() {
998 let dist = geo_distance_meters(coord, &point.position);
999 let radius = point.radius + tolerance_meters;
1000 if dist <= radius {
1001 let mut props = HashMap::new();
1002 props.insert(
1003 "pick_id".to_owned(),
1004 PropertyValue::Number(point.pick_id as f64),
1005 );
1006 props.insert("radius".to_owned(), PropertyValue::Number(point.radius));
1007 props.insert(
1008 "intensity".to_owned(),
1009 PropertyValue::Number(point.intensity as f64),
1010 );
1011 hits.push(PickHit {
1012 category: HitCategory::Feature,
1013 provenance: HitProvenance::GeometricApproximation,
1014 layer_id: Some(layer_name.to_owned()),
1015 source_id: None,
1016 source_layer: None,
1017 source_tile: None,
1018 feature_id: Some(format!("pt:{}", point.pick_id)),
1019 feature_index: Some(idx),
1020 geometry: None,
1021 properties: props,
1022 state: Default::default(),
1023 distance_meters: dist,
1024 hit_coord: Some(point.position),
1025 layer_priority,
1026 from_symbol: false,
1027 });
1028 }
1029 }
1030 }
1031 }
1032}
1033
1034fn model_hit_distance(instance: &ModelInstance, coord: &GeoCoord, tolerance: f64) -> Option<f64> {
1035 let dlat = (coord.lat - instance.position.lat).to_radians();
1036 let dlon = (coord.lon - instance.position.lon).to_radians();
1037 let a = (dlat / 2.0).sin().powi(2)
1038 + coord.lat.to_radians().cos()
1039 * instance.position.lat.to_radians().cos()
1040 * (dlon / 2.0).sin().powi(2);
1041 let c = 2.0 * a.sqrt().asin();
1042 let dist = 6_378_137.0 * c;
1043 let radius = model_horizontal_radius(instance) + tolerance;
1044 if dist <= radius {
1045 Some(dist)
1046 } else {
1047 None
1048 }
1049}
1050
1051fn model_horizontal_radius(instance: &ModelInstance) -> f64 {
1052 instance.scale * 0.5
1053}
1054
1055fn geo_distance_meters(a: &GeoCoord, b: &GeoCoord) -> f64 {
1056 let dlat = (a.lat - b.lat).to_radians();
1057 let dlon = (a.lon - b.lon).to_radians();
1058 let hav = (dlat / 2.0).sin().powi(2)
1059 + a.lat.to_radians().cos() * b.lat.to_radians().cos() * (dlon / 2.0).sin().powi(2);
1060 let c = 2.0 * hav.sqrt().asin();
1061 6_378_137.0 * c
1062}
1063
1064fn symbol_hit_distance_at_geo(
1065 symbol: &PlacedSymbol,
1066 coord: &GeoCoord,
1067 projection: CameraProjection,
1068) -> Option<f64> {
1069 let projected = projection.project(coord);
1070 let x = projected.position.x;
1071 let y = projected.position.y;
1072 let within = x >= symbol.collision_box.min[0]
1073 && x <= symbol.collision_box.max[0]
1074 && y >= symbol.collision_box.min[1]
1075 && y <= symbol.collision_box.max[1];
1076 within.then(|| geo_distance_meters(&symbol.anchor, coord))
1077}
1078
1079fn vector_render_mode_to_hit_category(mode: &crate::layers::VectorRenderMode) -> HitCategory {
1080 use crate::layers::VectorRenderMode;
1081 match mode {
1082 VectorRenderMode::Generic => HitCategory::Feature,
1083 VectorRenderMode::Fill => HitCategory::Feature,
1084 VectorRenderMode::Line => HitCategory::Feature,
1085 VectorRenderMode::Circle => HitCategory::Feature,
1086 VectorRenderMode::Symbol => HitCategory::Symbol,
1087 VectorRenderMode::Heatmap => HitCategory::Feature,
1088 VectorRenderMode::FillExtrusion => HitCategory::Feature,
1089 }
1090}
1091
1092pub(super) fn collect_terrain_samples_from_geometry(
1093 geometry: &crate::geometry::Geometry,
1094 terrain: &TerrainManager,
1095 samples: &mut Vec<(GeoCoord, f64)>,
1096) {
1097 use crate::geometry::Geometry;
1098 match geometry {
1099 Geometry::Point(p) => {
1100 if let Some(elev) = terrain.elevation_at(&p.coord) {
1101 samples.push((p.coord, elev));
1102 }
1103 }
1104 Geometry::LineString(ls) => {
1105 for coord in &ls.coords {
1106 if let Some(elev) = terrain.elevation_at(coord) {
1107 samples.push((*coord, elev));
1108 }
1109 }
1110 }
1111 Geometry::Polygon(poly) => {
1112 for coord in &poly.exterior {
1113 if let Some(elev) = terrain.elevation_at(coord) {
1114 samples.push((*coord, elev));
1115 }
1116 }
1117 for hole in &poly.interiors {
1118 for coord in hole {
1119 if let Some(elev) = terrain.elevation_at(coord) {
1120 samples.push((*coord, elev));
1121 }
1122 }
1123 }
1124 }
1125 Geometry::MultiPoint(mp) => {
1126 for p in &mp.points {
1127 if let Some(elev) = terrain.elevation_at(&p.coord) {
1128 samples.push((p.coord, elev));
1129 }
1130 }
1131 }
1132 Geometry::MultiLineString(mls) => {
1133 for ls in &mls.lines {
1134 for coord in &ls.coords {
1135 if let Some(elev) = terrain.elevation_at(coord) {
1136 samples.push((*coord, elev));
1137 }
1138 }
1139 }
1140 }
1141 Geometry::MultiPolygon(mpoly) => {
1142 for poly in &mpoly.polygons {
1143 for coord in &poly.exterior {
1144 if let Some(elev) = terrain.elevation_at(coord) {
1145 samples.push((*coord, elev));
1146 }
1147 }
1148 for hole in &poly.interiors {
1149 for coord in hole {
1150 if let Some(elev) = terrain.elevation_at(coord) {
1151 samples.push((*coord, elev));
1152 }
1153 }
1154 }
1155 }
1156 }
1157 Geometry::GeometryCollection(geoms) => {
1158 for g in geoms {
1159 collect_terrain_samples_from_geometry(g, terrain, samples);
1160 }
1161 }
1162 }
1163}