Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable shape file exports of a users property #137

Merged
merged 14 commits into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ Vagrantfile
.vagrant
*local_settings.py
landmapper/app/testing_files
landmapper/app/static/landmapper/shapefiles/
2 changes: 1 addition & 1 deletion landmapper/app/static/landmapper/css/report.css
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ p {
.icon {
display: block;
margin: .25em auto;
width: 2em;
width: 1.75em;
/* vertical-align: text-bottom; */
}

Expand Down
1 change: 1 addition & 0 deletions landmapper/app/static/landmapper/img/icon/icon-share.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions landmapper/app/static/landmapper/img/icon/icon-shp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions landmapper/app/static/landmapper/js/report.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

})();
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ <h2 class="d-print-none">All maps of your property</h2>
<img src="{% static 'landmapper/img/icon/icon-download.svg' %}" class="icon-download icon" />
<span>PDF</span>
</a>

<!-- Export Layer Button -->
<button id="export-layer-button" class="btn btn-primary d-print-none" data-property-id="{{ property_id }}">
<img src="{% static 'landmapper/img/icon/icon-shp.svg' %}" class="icon-shp icon" />
<span>SHP</span>
</button>

{% else %}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% load static %}

<button type="button" class="btn btn-primary copy-link" title="Copied to clipboard">
<img src="{% static 'landmapper/img/icon/icon-copy.svg' %}" class="icon-copy icon" />
<img src="{% static 'landmapper/img/icon/icon-share.svg' %}" class="icon-share icon" />
<span>Share</span>
</button>
1 change: 1 addition & 0 deletions landmapper/app/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/<str:property_id>/shp', export_layer, name='export_layer'),
# path('tinymce/', include('tinymce.urls')),
re_path(r'^tinymce/', include('tinymce.urls')),
]
77 changes: 73 additions & 4 deletions landmapper/app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
# """
Expand Down Expand Up @@ -714,20 +719,84 @@ 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:
Layer (default: property, leave modular to support forest_type, soil, others...)
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):
Expand Down
5 changes: 5 additions & 0 deletions landmapper/landmapper/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ###
###########################################
Expand Down