# PostGIS Integration - Dual-Mode Setup ## Overview ThrillWiki Django backend uses a **conditional PostGIS setup** that allows geographic data to work in both local development (SQLite) and production (PostgreSQL with PostGIS). ## How It Works ### Database Backends - **Local Development**: Uses regular SQLite without GIS extensions - Geographic coordinates stored in `latitude` and `longitude` DecimalFields - No spatial query capabilities - Simpler setup, easier for local development - **Production**: Uses PostgreSQL with PostGIS extension - Geographic coordinates stored in `location_point` PointField (PostGIS) - Full spatial query capabilities (distance calculations, geographic searches, etc.) - Automatically syncs with legacy `latitude`/`longitude` fields ### Model Implementation The `Park` model uses conditional field definition: ```python # Conditionally import GIS models only if using PostGIS backend _using_postgis = ( 'postgis' in settings.DATABASES['default']['ENGINE'] ) if _using_postgis: from django.contrib.gis.db import models as gis_models from django.contrib.gis.geos import Point ``` **Fields in SQLite mode:** - `latitude` (DecimalField) - Primary coordinate storage - `longitude` (DecimalField) - Primary coordinate storage **Fields in PostGIS mode:** - `location_point` (PointField) - Primary coordinate storage with GIS capabilities - `latitude` (DecimalField) - Deprecated, kept for backward compatibility - `longitude` (DecimalField) - Deprecated, kept for backward compatibility ### Helper Methods The Park model provides methods that work in both modes: #### `set_location(longitude, latitude)` Sets park location from coordinates. Works in both modes: - SQLite: Updates latitude/longitude fields - PostGIS: Updates location_point and syncs to latitude/longitude ```python park.set_location(-118.2437, 34.0522) ``` #### `coordinates` property Returns coordinates as `(longitude, latitude)` tuple: - SQLite: Returns from latitude/longitude fields - PostGIS: Returns from location_point (falls back to lat/lng if not set) ```python coords = park.coordinates # (-118.2437, 34.0522) ``` #### `latitude_value` property Returns latitude value: - SQLite: Returns from latitude field - PostGIS: Returns from location_point.y #### `longitude_value` property Returns longitude value: - SQLite: Returns from longitude field - PostGIS: Returns from location_point.x ## Setup Instructions ### Local Development (SQLite) 1. **No special setup required!** Just use the standard SQLite database: ```python # django/config/settings/local.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } ``` 2. Run migrations as normal: ```bash python manage.py migrate ``` 3. Use latitude/longitude fields for coordinates: ```python park = Park.objects.create( name="Test Park", latitude=40.7128, longitude=-74.0060 ) ``` ### Production (PostgreSQL with PostGIS) 1. **Install PostGIS extension in PostgreSQL:** ```sql CREATE EXTENSION postgis; ``` 2. **Configure production settings:** ```python # django/config/settings/production.py DATABASES = { 'default': { 'ENGINE': 'django.contrib.gis.db.backends.postgis', 'NAME': 'thrillwiki', 'USER': 'your_user', 'PASSWORD': 'your_password', 'HOST': 'your_host', 'PORT': '5432', } } ``` 3. **Run migrations:** ```bash python manage.py migrate ``` This will create the `location_point` PointField in addition to the latitude/longitude fields. 4. **Use location_point for geographic queries:** ```python from django.contrib.gis.geos import Point from django.contrib.gis.measure import D # Create park with PostGIS Point park = Park.objects.create( name="Test Park", location_point=Point(-118.2437, 34.0522, srid=4326) ) # Geographic queries (only in PostGIS mode) nearby_parks = Park.objects.filter( location_point__distance_lte=( Point(-118.2500, 34.0500, srid=4326), D(km=10) ) ) ``` ## Migration Strategy ### From SQLite to PostgreSQL When migrating from local development (SQLite) to production (PostgreSQL): 1. Export your data from SQLite 2. Set up PostgreSQL with PostGIS 3. Run migrations (will create location_point field) 4. Import your data (latitude/longitude fields will be populated) 5. Run a data migration to populate location_point from lat/lng: ```python # Example data migration from django.contrib.gis.geos import Point for park in Park.objects.filter(latitude__isnull=False, longitude__isnull=False): if not park.location_point: park.location_point = Point( float(park.longitude), float(park.latitude), srid=4326 ) park.save(update_fields=['location_point']) ``` ## Benefits 1. **Easy Local Development**: No need to install PostGIS or SpatiaLite for local development 2. **Production Power**: Full GIS capabilities in production with PostGIS 3. **Backward Compatible**: Keeps latitude/longitude fields for compatibility 4. **Unified API**: Helper methods work the same in both modes 5. **Gradual Migration**: Can migrate from SQLite to PostGIS without data loss ## Limitations ### In SQLite Mode (Local Development) - **No spatial queries**: Cannot use PostGIS query features like: - `distance_lte`, `distance_gte` (distance-based searches) - `dwithin` (within distance) - `contains`, `intersects` (geometric operations) - Geographic indexing for performance - **Workarounds for local development:** - Use simple filters on latitude/longitude ranges - Implement basic distance calculations in Python if needed - Most development work doesn't require spatial queries ### In PostGIS Mode (Production) - **Use location_point for queries**: Always use the `location_point` field for geographic queries, not lat/lng - **Sync fields**: If updating location_point directly, remember to sync to lat/lng if needed for compatibility ## Testing ### Test in SQLite (Local) ```bash cd django python manage.py shell # Test basic CRUD from apps.entities.models import Park from decimal import Decimal park = Park.objects.create( name="Test Park", park_type="theme_park", latitude=Decimal("40.7128"), longitude=Decimal("-74.0060") ) print(park.coordinates) # Should work print(park.latitude_value) # Should work ``` ### Test in PostGIS (Production) ```bash cd django python manage.py shell # Test GIS features from apps.entities.models import Park from django.contrib.gis.geos import Point from django.contrib.gis.measure import D park = Park.objects.create( name="Test Park", park_type="theme_park", location_point=Point(-118.2437, 34.0522, srid=4326) ) # Test distance query nearby = Park.objects.filter( location_point__distance_lte=( Point(-118.2500, 34.0500, srid=4326), D(km=10) ) ) ``` ## Future Considerations 1. **Remove Legacy Fields**: Once fully migrated to PostGIS in production and all code uses location_point, the latitude/longitude fields can be deprecated and eventually removed 2. **Add Spatial Indexes**: In production, add spatial indexes for better query performance: ```python class Meta: indexes = [ models.Index(fields=['location_point']), # Spatial index ] ``` 3. **Geographic Search API**: Build geographic search endpoints that work differently based on backend: - SQLite: Simple bounding box searches - PostGIS: Advanced spatial queries with distance calculations ## Troubleshooting ### "AttributeError: 'DatabaseOperations' object has no attribute 'geo_db_type'" This error occurs when trying to use PostGIS PointField with regular SQLite. Solution: - Ensure you're using the local.py settings which uses regular SQLite - Make sure migrations were created with SQLite active (no location_point field) ### "No such column: location_point" This occurs when: - Code tries to access location_point in SQLite mode - Solution: Use the helper methods (coordinates, latitude_value, longitude_value) instead ### "GDAL library not found" This occurs when django.contrib.gis is loaded but GDAL is not installed: - Even with SQLite, GDAL libraries must be available because django.contrib.gis is in INSTALLED_APPS - Install GDAL via Homebrew: `brew install gdal geos` - Configure paths in settings if needed ## References - [Django GIS Documentation](https://docs.djangoproject.com/en/stable/ref/contrib/gis/) - [PostGIS Documentation](https://postgis.net/documentation/) - [GeoDjango Tutorial](https://docs.djangoproject.com/en/stable/ref/contrib/gis/tutorial/)