11# Generated by Django 6.0.3 on 2026-04-08 13:29
22from __future__ import annotations
33
4+ import json
5+ from pathlib import Path
6+
7+ from django .conf import settings
48import django .contrib .gis .db .models .fields
9+ from django .contrib .gis .geos import GEOSGeometry
510from django .db import migrations , models
611import django .db .models .deletion
712
13+ DEFAULT_GEOJSON = settings .BASE_DIR / "bats_ai/core/data/species.geojson"
14+
15+
16+ def load_species_ranges_from_default_geojson (apps , schema_editor ) -> None :
17+ Species = apps .get_model ("core" , "Species" )
18+ SpeciesRange = apps .get_model ("core" , "SpeciesRange" )
19+
20+ path = Path (DEFAULT_GEOJSON ).resolve ()
21+ if not path .is_file ():
22+ raise FileNotFoundError (f"GeoJSON file not found: { path } " )
23+
24+ doc = json .loads (path .read_text (encoding = "utf-8" ))
25+ if doc .get ("type" ) != "FeatureCollection" :
26+ raise ValueError ('Expected a GeoJSON FeatureCollection ("type": "FeatureCollection").' )
27+
28+ features = doc .get ("features" ) or []
29+ for feature in features :
30+ props = feature .get ("properties" ) or {}
31+ code = props .get ("name" )
32+ if not code or not str (code ).strip ():
33+ continue
34+ code = str (code ).strip ().upper ()
35+
36+ geom_json = feature .get ("geometry" )
37+ if not geom_json :
38+ continue
39+
40+ geom = GEOSGeometry (json .dumps (geom_json ))
41+ if geom .srid in (None , 0 ):
42+ geom .srid = 4326
43+ elif geom .srid != 4326 :
44+ geom .transform (4326 )
45+
46+ species = Species .objects .filter (species_code__iexact = code ).first ()
47+ if species is None :
48+ continue
49+
50+ fid = props .get ("id" )
51+ source_feature_id = str (fid ) if fid is not None else ""
52+ SpeciesRange .objects .update_or_create (
53+ species = species ,
54+ defaults = {
55+ "geom" : geom ,
56+ "source_feature_id" : source_feature_id ,
57+ },
58+ )
59+
860
961class Migration (migrations .Migration ):
1062 dependencies = [
@@ -23,15 +75,21 @@ class Migration(migrations.Migration):
2375 (
2476 "id" ,
2577 models .BigAutoField (
26- auto_created = True , primary_key = True , serialize = False , verbose_name = "ID"
78+ auto_created = True ,
79+ primary_key = True ,
80+ serialize = False ,
81+ verbose_name = "ID" ,
2782 ),
2883 ),
29- ("geom" , django .contrib .gis .db .models .fields .GeometryField (srid = 4326 )),
84+ (
85+ "geom" ,
86+ django .contrib .gis .db .models .fields .GeometryField (srid = 4326 ),
87+ ),
3088 (
3189 "source_feature_id" ,
3290 models .CharField (
3391 blank = True ,
34- help_text = "Optional id from the source GeoJSON feature properties." ,
92+ help_text = ( "Optional id from the source GeoJSON feature properties." ) ,
3593 max_length = 255 ,
3694 ),
3795 ),
@@ -45,4 +103,8 @@ class Migration(migrations.Migration):
45103 ),
46104 ],
47105 ),
106+ migrations .RunPython (
107+ load_species_ranges_from_default_geojson ,
108+ migrations .RunPython .noop ,
109+ ),
48110 ]
0 commit comments