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