Skip to content

Commit

Permalink
Merge pull request #1124 from kartoza/feature/1108-editable-site-loca…
Browse files Browse the repository at this point in the history
…tion

Feature/1108 editable site location
  • Loading branch information
tinashechiraya authored Oct 17, 2024
2 parents 261e302 + 2e47a38 commit 3f332d2
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 14 deletions.
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ shell:
@echo "------------------------------------------------------------------"
@docker-compose -p $(PROJECT_ID) exec django /bin/bash

dev-shell:
@echo
@echo "------------------------------------------------------------------"
@echo "Shelling in in development mode"
@echo "------------------------------------------------------------------"
@docker-compose -p $(PROJECT_ID) exec dev /bin/bash

db-bash:
@echo
@echo "------------------------------------------------------------------"
Expand Down
2 changes: 1 addition & 1 deletion django_project/minisass_frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 41 additions & 13 deletions django_project/monitor/admin.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import csv
from collections import OrderedDict
from django import forms
from django.conf import settings
from django.contrib import admin
from django.utils.encoding import smart_str
from django.contrib.gis import admin as geo_admin
from django.contrib.gis.geos import Point
from django.http import HttpResponse
from django.utils.encoding import smart_str
from django.utils.safestring import mark_safe
from leaflet.admin import LeafletGeoAdmin
from minisass_authentication.models import UserProfile
from monitor.forms import ObservationPestImageForm, CustomGeoAdminForm
from django.contrib.sites.models import Site

from monitor.forms import ObservationPestImageForm
from .models import (
Sites,
Observations,
Expand Down Expand Up @@ -174,7 +181,28 @@ class SiteImageInline(admin.TabularInline):


@admin.register(Sites)
class SitesAdmin(admin.ModelAdmin):
class SitesAdmin(geo_admin.OSMGeoAdmin):
form = CustomGeoAdminForm
class Media:
js = (
'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js', # Ensure jQuery is available
'admin/js/latlon_map_update.js', # Custom JS file to handle lat/lon updates
)

def latitude(self, obj):
return obj.the_geom.y if obj.the_geom else None

def longitude(self, obj):
return obj.the_geom.x if obj.the_geom else None

latitude.short_description = 'Latitude'
longitude.short_description = 'Longitude'

def render_change_form(self, request, context, *args, **kwargs):
context['osm_widget'] = True
return super().render_change_form(request, context, *args, **kwargs)


list_max_show_all = 1000
list_display = (
'site_name',
Expand Down Expand Up @@ -213,15 +241,15 @@ def download_selected_sites(self, request, queryset):
writer = csv.writer(response)
writer.writerow(
[
'Site Name',
'River Name',
'Description',
'Site Name',
'River Name',
'Description',
'River Category',
'Site Location',
'Created By',
'User Organization Name',
'User Expert Status',
'User Country',
'User Country',
'Site Creation Date'
])

Expand All @@ -237,14 +265,14 @@ def download_selected_sites(self, request, queryset):
user_is_expert = False
writer.writerow(
[
smart_str(site.site_name),
smart_str(site.river_name),
smart_str(site.description),
smart_str(site.river_cat),
smart_str(site.site_name),
smart_str(site.river_name),
smart_str(site.description),
smart_str(site.river_cat),
smart_str(f"Longitude: {site.the_geom.x}, Latitude: {site.the_geom.y}"),
smart_str(site.user.email),
smart_str(user_organization_name),
smart_str(user_is_expert),
smart_str(user_is_expert),
smart_str(user_country),
smart_str(site.time_stamp)
]
Expand All @@ -258,4 +286,4 @@ def download_selected_sites(self, request, queryset):


admin.site.register(ObservationPestImage)
admin.site.unregister(Site)
admin.site.unregister(Site)
29 changes: 29 additions & 0 deletions django_project/monitor/forms.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django import forms
from django.contrib.gis.geos import Point
from django.forms import ModelForm, Textarea, Select, DateInput

from monitor.models import Sites, Observations, ObservationPestImage
Expand Down Expand Up @@ -66,3 +67,31 @@ class MapForm(forms.Form):
edit_site = forms.CharField()
error = forms.CharField()
saved_obs = forms.CharField()


class CustomGeoAdminForm(forms.ModelForm):
latitude = forms.FloatField(required=False, label='Latitude')
longitude = forms.FloatField(required=False, label='Longitude')

class Meta:
model = Sites
fields = ['the_geom', 'latitude', 'longitude', 'site_name', 'river_name',
'description', 'river_cat', 'user', 'assessment']

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

# Pre-fill lat/lng from the existing point field if available
if self.instance and self.instance.the_geom:
self.fields['latitude'].initial = self.instance.the_geom.y
self.fields['longitude'].initial = self.instance.the_geom.x

def clean(self):
cleaned_data = super().clean()
lat = cleaned_data.get('latitude')
lng = cleaned_data.get('longitude')

if lat is not None and lng is not None:
cleaned_data['the_geom'] = Point(lng, lat)

return cleaned_data
107 changes: 107 additions & 0 deletions django_project/monitor/static/admin/js/latlon_map_update.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
(function($) {
$(document).ready(function() {
var $latField = $('#id_latitude');
var $lonField = $('#id_longitude');
var mapWidget = window.geodjango_the_geom.map;

if (!mapWidget) {
return;
}

// Helper function to find the vector layer containing the point
function getVectorLayer() {
var vectorLayer = null;

// Loop through all layers in the map to find a vector layer
for (var i = 0; i < mapWidget.layers.length; i++) {
var layer = mapWidget.layers[i];
if (layer.CLASS_NAME === 'OpenLayers.Layer.Vector') {
vectorLayer = layer;
break;
}
}

return vectorLayer;
}

var vectorLayer = getVectorLayer();

// If the vector layer exists, attach event listeners to it
if (vectorLayer) {
// Listen for changes to features (points) on the vector layer
vectorLayer.events.on({
'featuremodified': function(event) {
var geometry = event.feature.geometry;
var lonLat = new OpenLayers.LonLat(geometry.x, geometry.y).transform(
mapWidget.getProjectionObject(),
new OpenLayers.Projection("EPSG:4326") // To WGS84
);

// Update lat/lon fields based on new point location
$latField.val(lonLat.lat.toFixed(8));
$lonField.val(lonLat.lon.toFixed(8));
},
'featureadded': function(event) {
var geometry = event.feature.geometry;
var lonLat = new OpenLayers.LonLat(geometry.x, geometry.y).transform(
mapWidget.getProjectionObject(),
new OpenLayers.Projection("EPSG:4326") // To WGS84
);

// Update lat/lon fields when a new point is added
$latField.val(lonLat.lat.toFixed(8));
$lonField.val(lonLat.lon.toFixed(8));
}
});
}

// Update the map based on lat/lon fields when they are changed
function updateMapFromFields() {
vectorLayer.removeAllFeatures();
var lat = parseFloat($latField.val());
var lon = parseFloat($lonField.val());

if (!isNaN(lat) && !isNaN(lon)) {
var lonLat = new OpenLayers.LonLat(lon, lat).transform(
new OpenLayers.Projection("EPSG:4326"), // From WGS84
mapWidget.getProjectionObject() // To map projection
);

var pointGeometry = new OpenLayers.Geometry.Point(lonLat.lon, lonLat.lat);

// Update the existing feature with new geometry
if (vectorLayer.features.length > 0) {
vectorLayer.features[0].geometry = pointGeometry;
vectorLayer.drawFeature(vectorLayer.features[0]);
} else {
// Add a new feature if none exist
var feature = new OpenLayers.Feature.Vector(pointGeometry);
vectorLayer.addFeatures([feature]);
}

// Recenter the map to the new location
mapWidget.setCenter(lonLat, mapWidget.getZoom());
}
}

// Debounce function: delays execution until user stops typing
function debounce(func, delay) {
var timeout;
return function() {
var context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
func.apply(context, args);
}, delay);
};
}

// Attach debounced event listeners to the lat/lon fields
var debouncedUpdateMap = debounce(updateMapFromFields, 500); // 500ms delay

// Attach event listeners to the lat/lon fields to update the map when they change
$latField.on('input', debouncedUpdateMap);
$lonField.on('input', debouncedUpdateMap);

});
})(django.jQuery);

0 comments on commit 3f332d2

Please sign in to comment.