diff --git a/.gitignore b/.gitignore
index 75cf1aa..dac0034 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ Vagrantfile
.vagrant
*local_settings.py
landmapper/app/testing_files
+landmapper/app/static/landmapper/shapefiles/
\ No newline at end of file
diff --git a/landmapper/app/static/landmapper/css/report.css b/landmapper/app/static/landmapper/css/report.css
index 66f7974..fd27987 100644
--- a/landmapper/app/static/landmapper/css/report.css
+++ b/landmapper/app/static/landmapper/css/report.css
@@ -128,7 +128,7 @@ p {
.icon {
display: block;
margin: .25em auto;
- width: 2em;
+ width: 1.75em;
/* vertical-align: text-bottom; */
}
diff --git a/landmapper/app/static/landmapper/img/icon/icon-share.svg b/landmapper/app/static/landmapper/img/icon/icon-share.svg
new file mode 100644
index 0000000..454579f
--- /dev/null
+++ b/landmapper/app/static/landmapper/img/icon/icon-share.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/landmapper/app/static/landmapper/img/icon/icon-shp.svg b/landmapper/app/static/landmapper/img/icon/icon-shp.svg
new file mode 100644
index 0000000..79e6673
--- /dev/null
+++ b/landmapper/app/static/landmapper/img/icon/icon-shp.svg
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/landmapper/app/static/landmapper/js/report.js b/landmapper/app/static/landmapper/js/report.js
index c664dbe..b6a8cf4 100644
--- a/landmapper/app/static/landmapper/js/report.js
+++ b/landmapper/app/static/landmapper/js/report.js
@@ -29,4 +29,47 @@ let newPropertyID = documentPropertyIdSplit.join('%7C');
if (copyToAccountBtn) {
copyToAccountBtn.href = `/landmapper/report/${newPropertyID}`;
}
+
+
+ /**
+ * Add event listener to the export layer button
+ */
+ function exportLayerHandler() {
+ const propertyId = this.getAttribute('data-property-id');
+ fetch(`/export_layer/${propertyId}/shp`, {
+ method: 'GET',
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest'
+ }
+ })
+ .then(response => response.ok ? response.blob() : response.text().then(text => { throw new Error(text); }))
+ .then(blob => {
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.style.display = 'none';
+ a.href = url;
+ a.download = `${propertyId}.zip`;
+ document.body.appendChild(a);
+ a.click();
+ window.URL.revokeObjectURL(url);
+ })
+ .catch(error => {
+ console.error('Error:', error);
+ alert('An error occurred while exporting the layer.');
+ });
+ }
+
+ function setupExportLayerButton() {
+ const exportLayerButton = document.getElementById('export-layer-button');
+ if (exportLayerButton) {
+ exportLayerButton.addEventListener('click', exportLayerHandler);
+ }
+ }
+
+ function init() {
+ setupExportLayerButton();
+ }
+
+ document.addEventListener('DOMContentLoaded', init);
+
})();
\ No newline at end of file
diff --git a/landmapper/app/templates/landmapper/report/report-overview.html b/landmapper/app/templates/landmapper/report/report-overview.html
index e4543c4..6a5d868 100644
--- a/landmapper/app/templates/landmapper/report/report-overview.html
+++ b/landmapper/app/templates/landmapper/report/report-overview.html
@@ -67,6 +67,12 @@
All maps of your property
PDF
+
+
+
{% else %}
diff --git a/landmapper/app/templates/landmapper/report/report-share.html b/landmapper/app/templates/landmapper/report/report-share.html
index 86ce0bd..8d8410a 100644
--- a/landmapper/app/templates/landmapper/report/report-share.html
+++ b/landmapper/app/templates/landmapper/report/report-share.html
@@ -1,6 +1,6 @@
{% load static %}
\ No newline at end of file
diff --git a/landmapper/app/urls.py b/landmapper/app/urls.py
index 40e17ed..a75b8ad 100644
--- a/landmapper/app/urls.py
+++ b/landmapper/app/urls.py
@@ -46,6 +46,7 @@
path('admin_export_property_records/', exportPropertyRecords, name='export_property_records'),
path('accounts/profile/', homeRedirect, name='account_confirm_email'),
path('auth/email/', homeRedirect, name='auth_email'),
+ path('export_layer//shp', export_layer, name='export_layer'),
# path('tinymce/', include('tinymce.urls')),
re_path(r'^tinymce/', include('tinymce.urls')),
]
diff --git a/landmapper/app/views.py b/landmapper/app/views.py
index 094dee3..1497d64 100644
--- a/landmapper/app/views.py
+++ b/landmapper/app/views.py
@@ -27,13 +27,18 @@
from django.views.decorators.csrf import csrf_protect
from django.views.generic.edit import FormView
from flatblocks.models import FlatBlock
+import os
from PIL import Image
import requests
import ssl
+import subprocess
import sys
from urllib.error import URLError
from urllib.parse import quote
import urllib.request, urllib.parse
+import logging
+
+logger = logging.getLogger(__name__)
def unstable_request_wrapper(url, params=False, retries=0):
# """
@@ -714,8 +719,40 @@ def get_property_pdf_georef(request, property_id, map_type="aerial"):
pass
return response
-## BELONGS IN VIEWS.py
-def export_layer(request):
+#*
+#* Export shapefile
+#*
+@login_required(login_url='/auth/login/')
+def export_shapefile(db_user, db_pw_command, database_name, shpdir, filename, query):
+ """
+ Helper function to export a shapefile using pgsql2shp.
+ """
+ export_command = f"pgsql2shp -u {db_user}{db_pw_command} -f {shpdir}/{filename} {database_name} \"{query}\""
+ subprocess.run(export_command, shell=True, check=True)
+
+#*
+#* Zip Shapefile
+#*
+def zip_shapefile(shpdir, filename):
+ """
+ Helper function to zip the shapefile.
+ """
+ import io
+ import zipfile
+
+ os.chdir(shpdir)
+ files = [f for f in os.listdir(shpdir) if f.startswith(filename)]
+ zip_buffer = io.BytesIO()
+ with zipfile.ZipFile(zip_buffer, mode="w", compression=zipfile.ZIP_DEFLATED) as zf:
+ for file in files:
+ zf.write(file)
+ zip_buffer.seek(0)
+ return zip_buffer
+
+#*
+#* Export Layer
+#*
+def export_layer(request, property_id):
'''
(called on request for download GIS data)
IN:
@@ -723,11 +760,43 @@ def export_layer(request):
Format (default: zipped .shp, leave modular to support json & others)
property
OUT:
- layer file
+ property layer in requested format
USES:
pgsql2shp (OGR/PostGIS built-in)
'''
- return render(request, 'landmapper/base.html', {})
+ if not request.user.is_authenticated:
+ return HttpResponse('User not authenticated. Please log in.', status=401)
+
+ try:
+ property_record = properties.get_property_by_id(property_id, request.user)
+ except PropertyRecord.DoesNotExist:
+ return HttpResponse('Property not found or you do not have permission to access it.', status=404)
+
+ db_user = settings.DATABASES['default']['USER']
+ db_pw_command = f" -P {settings.DATABASES['default']['PASSWORD']}" if settings.DATABASES['default']['PASSWORD'] else ""
+ database_name = settings.DATABASES['default']['NAME']
+ filename = f"{property_record.name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
+ shpdir = os.path.join(settings.SHAPEFILE_EXPORT_DIR, filename)
+ os.makedirs(shpdir, exist_ok=True)
+
+ try:
+ query = f"SELECT * FROM app_propertyrecord WHERE id = {property_record.id};"
+ export_shapefile(db_user, db_pw_command, database_name, shpdir, filename, query)
+ zip_buffer = zip_shapefile(shpdir, filename)
+
+ # Return zipped shapefile
+ response = HttpResponse(zip_buffer.read(), content_type='application/zip')
+ response['Content-Disposition'] = f'attachment; filename={filename}.zip'
+ return response
+ except subprocess.CalledProcessError as e:
+ logger.error(f"Error exporting shapefile: {e}")
+ return HttpResponse('Error exporting shapefile.', status=500)
+ finally:
+ # Clean up temporary files
+ for file in os.listdir(shpdir):
+ os.remove(os.path.join(shpdir, file))
+ os.rmdir(shpdir)
+
### REGISTRATION/ACCOUNT MANAGEMENT ###
def accountsRedirect(request):
diff --git a/landmapper/landmapper/settings.py b/landmapper/landmapper/settings.py
index 31dc580..ec8b1a6 100644
--- a/landmapper/landmapper/settings.py
+++ b/landmapper/landmapper/settings.py
@@ -1509,6 +1509,11 @@
TESTING_DIR = os.path.join(APP_DIR, 'testing_files')
IMAGE_TEST_DIR = os.path.join(TESTING_DIR, 'image_test')
+###########################################
+## Shapefile Export ###
+###########################################
+SHAPEFILE_EXPORT_DIR = os.path.join(APP_DIR, 'static/landmapper/shapefiles/')
+
###########################################
## PDF Files ###
###########################################