@@ -39,7 +39,7 @@ def set_bounds(self):
3939 self .bounds = Polygon .from_bbox (
4040 (bbox ['xmin' ], bbox ['ymin' ], bbox ['xmax' ], bbox ['ymax' ])
4141 )
42- elif isinstance (self , VectorMapLayer ):
42+ elif isinstance (self , ( VectorMapLayer , FMVLayer ) ):
4343 geojson_data = self .read_geojson_data ()
4444 if 'features' in geojson_data :
4545 geometries = [shape (feature ['geometry' ]) for feature in geojson_data ['features' ]]
@@ -104,6 +104,66 @@ def read_geojson_data(self) -> dict:
104104 return json .load (self .geojson_file .open ())
105105
106106
107+ class FMVLayer (AbstractMapLayer ):
108+ fmv_source_video = S3FileField (null = True )
109+ fmv_video = S3FileField (null = True )
110+ geojson_file = S3FileField (null = True )
111+
112+ def write_geojson_data (self , content : str | dict ):
113+ if isinstance (content , str ):
114+ data = content
115+ elif isinstance (content , dict ):
116+ data = json .dumps (content )
117+ else :
118+ raise Exception (f'Invalid content type supplied: { type (content )} ' )
119+
120+ self .geojson_file .save ('vectordata.geojson' , ContentFile (data .encode ()))
121+
122+ def read_geojson_data (self ) -> dict :
123+ """Read and load the data from geojson_file into a dict."""
124+ return json .load (self .geojson_file .open ())
125+
126+ def get_ground_frame_mapping (self ) -> dict :
127+ """Return a mapping from frameId -> 4-point polygon coordinates."""
128+ result = {}
129+ features = FMVVectorFeature .objects .filter (
130+ map_layer = self ,
131+ properties__type = 'ground_frame' ,
132+ ).exclude (properties__frameId__isnull = True )
133+
134+ for feature in features :
135+ frame_id = feature .properties .get ("frameId" )
136+ geom = feature .geometry
137+
138+ if geom .geom_type == "Polygon" :
139+ coords = list (geom .exterior .coords )
140+ if len (coords ) >= 4 :
141+ # Return first 4 unique corners, not repeating the closing point
142+ result [frame_id ] = coords [:4 ]
143+ elif geom .geom_type == "MultiPolygon" :
144+ # Pick the largest polygon by area, then return its first 4 corners
145+ largest = max (geom , key = lambda p : p .area )
146+ coords = list (largest .exterior .coords )
147+ if len (coords ) >= 4 :
148+ result [frame_id ] = coords [:4 ]
149+ else :
150+ # Optional: log or skip unexpected geometries
151+ continue
152+
153+ return result
154+
155+
156+ @receiver (models .signals .pre_delete , sender = FMVLayer )
157+ def delete__fmvvectorcontent (sender , instance , ** kwargs ):
158+ if instance .geojson_file :
159+ instance .geojson_file .delete (save = False )
160+
161+ class FMVVectorFeature (models .Model ):
162+ map_layer = models .ForeignKey (FMVLayer , on_delete = models .CASCADE )
163+ geometry = geomodels .GeometryField ()
164+ properties = models .JSONField ()
165+
166+
107167@receiver (models .signals .pre_delete , sender = VectorMapLayer )
108168def delete__vectorcontent (sender , instance , ** kwargs ):
109169 if instance .geojson_file :
0 commit comments