11use std:: iter:: once;
2+ use std:: sync:: Arc ;
23
34use chrono:: { DateTime , Utc } ;
45use egui:: Color32 ;
56use itertools:: Either ;
6- use serde:: { Deserialize , Serialize } ;
7+ use serde:: { Deserialize , Deserializer , Serialize , Serializer } ;
78
89use super :: coordinates:: { BoundingBox , Coordinate , PixelCoordinate , WGS84Coordinate } ;
910
@@ -285,6 +286,30 @@ pub enum Geometry<C: Coordinate> {
285286 Point ( C , Metadata ) ,
286287 LineString ( Vec < C > , Metadata ) ,
287288 Polygon ( Vec < C > , Metadata ) ,
289+ /// Heatmap density layer. Wrapped in `Arc` so the (potentially huge)
290+ /// coordinate buffer is shared across tile-render clones rather than copied.
291+ Heatmap (
292+ #[ serde( serialize_with = "serialize_arc_vec" ) ]
293+ #[ serde( deserialize_with = "deserialize_arc_vec" ) ]
294+ Arc < Vec < C > > ,
295+ Metadata ,
296+ ) ,
297+ }
298+
299+ fn serialize_arc_vec < S , T > ( arc : & Arc < Vec < T > > , ser : S ) -> Result < S :: Ok , S :: Error >
300+ where
301+ S : Serializer ,
302+ T : Serialize ,
303+ {
304+ arc. as_ref ( ) . serialize ( ser)
305+ }
306+
307+ fn deserialize_arc_vec < ' de , D , T > ( de : D ) -> Result < Arc < Vec < T > > , D :: Error >
308+ where
309+ D : Deserializer < ' de > ,
310+ T : Deserialize < ' de > ,
311+ {
312+ Ok ( Arc :: new ( Vec :: < T > :: deserialize ( de) ?) )
288313}
289314
290315impl From < Geometry < WGS84Coordinate > > for Geometry < PixelCoordinate > {
@@ -309,6 +334,15 @@ impl From<Geometry<WGS84Coordinate>> for Geometry<PixelCoordinate> {
309334 . collect ( ) ,
310335 metadata,
311336 ) ,
337+ Geometry :: Heatmap ( coords, metadata) => Geometry :: Heatmap (
338+ Arc :: new (
339+ coords
340+ . iter ( )
341+ . map ( super :: coordinates:: Coordinate :: as_pixel_coordinate)
342+ . collect :: < Vec < _ > > ( ) ,
343+ ) ,
344+ metadata,
345+ ) ,
312346 }
313347 }
314348}
@@ -338,6 +372,10 @@ impl<C: Coordinate> Geometry<C> {
338372 . iter ( )
339373 . map ( |c| BoundingBox :: from_iterator ( once ( * c) ) )
340374 . fold ( BoundingBox :: default ( ) , |acc, b| acc. extend ( & b) ) ,
375+ Geometry :: Heatmap ( coords, _) => coords
376+ . iter ( )
377+ . map ( |c| BoundingBox :: from_iterator ( once ( * c) ) )
378+ . fold ( BoundingBox :: default ( ) , |acc, b| acc. extend ( & b) ) ,
341379 }
342380 }
343381
@@ -346,7 +384,8 @@ impl<C: Coordinate> Geometry<C> {
346384 Geometry :: GeometryCollection ( _, metadata)
347385 | Geometry :: Point ( _, metadata)
348386 | Geometry :: Polygon ( _, metadata)
349- | Geometry :: LineString ( _, metadata) => metadata. style . as_ref ( ) . is_none_or ( |s| s. visible ) ,
387+ | Geometry :: LineString ( _, metadata)
388+ | Geometry :: Heatmap ( _, metadata) => metadata. style . as_ref ( ) . is_none_or ( |s| s. visible ) ,
350389 }
351390 }
352391
@@ -362,7 +401,8 @@ impl<C: Coordinate> Geometry<C> {
362401 }
363402 Geometry :: Point ( _, metadata)
364403 | Geometry :: LineString ( _, metadata)
365- | Geometry :: Polygon ( _, metadata) => {
404+ | Geometry :: Polygon ( _, metadata)
405+ | Geometry :: Heatmap ( _, metadata) => {
366406 self . is_visible ( ) && metadata. is_visible_at_time ( current_time)
367407 }
368408 }
@@ -392,7 +432,8 @@ impl<C: Coordinate> Geometry<C> {
392432 Geometry :: GeometryCollection ( _, metadata)
393433 | Geometry :: Point ( _, metadata)
394434 | Geometry :: Polygon ( _, metadata)
395- | Geometry :: LineString ( _, metadata) => {
435+ | Geometry :: LineString ( _, metadata)
436+ | Geometry :: Heatmap ( _, metadata) => {
396437 metadata. style = Some ( style. clone ( ) ) ;
397438 }
398439 }
@@ -405,7 +446,8 @@ impl<C: Coordinate> Geometry<C> {
405446 Geometry :: GeometryCollection ( _, metadata)
406447 | Geometry :: Point ( _, metadata)
407448 | Geometry :: Polygon ( _, metadata)
408- | Geometry :: LineString ( _, metadata) => & metadata. style ,
449+ | Geometry :: LineString ( _, metadata)
450+ | Geometry :: Heatmap ( _, metadata) => & metadata. style ,
409451 }
410452 }
411453}
@@ -675,6 +717,57 @@ mod tests {
675717 assert ! ( geom. is_visible( ) ) ;
676718 }
677719
720+ #[ test]
721+ fn heatmap_bounding_box_unions_all_points ( ) {
722+ let geom = Geometry :: Heatmap (
723+ Arc :: new ( vec ! [
724+ PixelCoordinate :: new( 1.0 , 2.0 ) ,
725+ PixelCoordinate :: new( 5.0 , 8.0 ) ,
726+ PixelCoordinate :: new( -3.0 , 4.0 ) ,
727+ ] ) ,
728+ Metadata :: default ( ) ,
729+ ) ;
730+ let bbox = geom. bounding_box ( ) ;
731+ assert ! ( bbox. is_valid( ) ) ;
732+ assert ! ( ( bbox. min_x( ) - -3.0 ) . abs( ) < 1e-6 ) ;
733+ assert ! ( ( bbox. max_x( ) - 5.0 ) . abs( ) < 1e-6 ) ;
734+ assert ! ( ( bbox. min_y( ) - 2.0 ) . abs( ) < 1e-6 ) ;
735+ assert ! ( ( bbox. max_y( ) - 8.0 ) . abs( ) < 1e-6 ) ;
736+ }
737+
738+ #[ test]
739+ fn heatmap_visibility_respects_style ( ) {
740+ let geom = Geometry :: Heatmap (
741+ Arc :: new ( vec ! [ PixelCoordinate :: new( 0.0 , 0.0 ) ] ) ,
742+ Metadata :: default ( ) . with_style ( Style :: default ( ) . with_visible ( false ) ) ,
743+ ) ;
744+ assert ! ( !geom. is_visible( ) ) ;
745+ }
746+
747+ #[ test]
748+ fn heatmap_wgs84_to_pixel_conversion ( ) {
749+ let geom: Geometry < WGS84Coordinate > = Geometry :: Heatmap (
750+ Arc :: new ( vec ! [ WGS84Coordinate { lat: 0.0 , lon: 0.0 } ] ) ,
751+ Metadata :: default ( ) ,
752+ ) ;
753+ let pixel: Geometry < PixelCoordinate > = geom. into ( ) ;
754+ assert ! ( matches!( pixel, Geometry :: Heatmap ( _, _) ) ) ;
755+ }
756+
757+ #[ test]
758+ fn heatmap_clone_shares_buffer ( ) {
759+ let geom = Geometry :: Heatmap (
760+ Arc :: new ( vec ! [ PixelCoordinate :: new( 0.0 , 0.0 ) ; 1024 ] ) ,
761+ Metadata :: default ( ) ,
762+ ) ;
763+ let cloned = geom. clone ( ) ;
764+ if let ( Geometry :: Heatmap ( a, _) , Geometry :: Heatmap ( b, _) ) = ( & geom, & cloned) {
765+ assert ! ( Arc :: ptr_eq( a, b) , "cloning Heatmap must share the Arc buffer" ) ;
766+ } else {
767+ panic ! ( "expected Heatmap variants" ) ;
768+ }
769+ }
770+
678771 #[ test]
679772 fn geometry_is_visible_with_hidden_style ( ) {
680773 let geom = Geometry :: Point (
0 commit comments