mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 05:31:16 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
297
django-backend/POSTGIS_SETUP.md
Normal file
297
django-backend/POSTGIS_SETUP.md
Normal file
@@ -0,0 +1,297 @@
|
||||
# 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/)
|
||||
Reference in New Issue
Block a user