Skip to content

Commit

Permalink
[WIP] customizable export UI for DistortableCollection (#485)
Browse files Browse the repository at this point in the history
* in progress

* additions

* rebased and adjusted for Promise use

* small changes/fixes

* changed options param ref

* almost working

* built

* working export generation; need to handle response

* cleanup

* reverting unintended boxcollect changes

* stop spinner on completion

* version bump for customizable export processes

* cleanup

* resolve linting errors

* resolve dumb error

* v0.10.0
  • Loading branch information
jywarren authored Jan 10, 2020
1 parent 4b3e6c9 commit f1c260d
Show file tree
Hide file tree
Showing 5 changed files with 314 additions and 142 deletions.
147 changes: 77 additions & 70 deletions dist/leaflet.distortableimage.js
Original file line number Diff line number Diff line change
Expand Up @@ -1787,7 +1787,6 @@ L.ExportAction = L.EditAction.extend({
L.IconUtil.toggleXlink(this._link, 'get_app', 'spinner');
L.IconUtil.toggleTitle(this._link, 'Export Images', 'Loading...');
L.IconUtil.addClassToSvg(this._link, 'loader');

edit.startExport().then(function() {
L.IconUtil.toggleXlink(this._link, 'get_app', 'spinner');
L.IconUtil.toggleTitle(this._link, 'Export Images', 'Loading...');
Expand Down Expand Up @@ -2757,9 +2756,86 @@ L.DistortableCollection.Edit = L.Handler.extend({

initialize: function(group, options) {
this._group = group;

this.startExport = options.startExport || function startExport() {
return new Promise(function(resolve) {
var opts = group.options || {};

// this may be overridden to update the UI to show export progress or completion
// eslint-disable-next-line require-jsdoc
function _defaultUpdater(data, _opts) {
data = JSON.parse(data);
// optimization: fetch status directly from google storage:
if (_opts.statusUrl !== data.status_url && data.status_url.match('.json')) {
if (data.status_url && data.status_url.substr(0,1) === "/") {
_opts.statusUrl = _opts.exportUrl + data.status_url;
} else {
_opts.statusUrl = data.status_url;
}
}
if (data.status === 'complete') {
clearInterval(_opts.updateInterval);
resolve();
}
if (data.status === 'complete' && data.jpg !== null) {
alert('Export succeeded. ' + _opts.exportUrl + data.jpg);
}
// TODO: update to clearInterval when status == "failed" if we update that in this file:
// https://github.com/publiclab/mapknitter-exporter/blob/main/lib/mapknitterExporter.rb
console.log(data);
}

// receives the URL of status.json, and starts running the updater to repeatedly fetch from status.json;
// this may be overridden to integrate with any UI
// eslint-disable-next-line require-jsdoc
function _defaultHandleStatusResponse(data, _opts) {

// repeatedly fetch the status.json
_opts.updateInterval = setInterval(function intervalUpdater() {
$.ajax(_opts.statusUrl + '?' + Date.now(), {// bust cache with timestamp
type: 'GET',
crossDomain: true,
}).done(function(data) {
_opts.updater(data, _opts);
});
}, _opts.frequency);
}

// initiate the export
// eslint-disable-next-line require-jsdoc
function _defaultFetchStatusUrl(_opts) {
$.ajax({
url: _opts.exportStartUrl,
crossDomain: true,
type: 'POST',
data: {
collection: JSON.stringify(_opts.collection),
scale: _opts.scale,
upload: true
},
success: function(data) { _opts.handleStatusResponse(data, _opts); }, // this handles the initial response
});
}

opts.resolve = resolve; // allow user-specified functions to resolve the promise
opts.collection = opts.collection || this._group.generateExportJson();
opts.frequency = opts.frequency || 3000;
opts.scale = opts.scale || 100; // switch it to _getAvgCmPerPixel !
opts.updater = opts.updater || _defaultUpdater;
opts.handleStatusResponse = opts.handleStatusResponse || _defaultHandleStatusResponse;
opts.fetchStatusUrl = opts.fetchStatusUrl || _defaultFetchStatusUrl;
opts.exportStartUrl = opts.exportStartUrl || '//export.mapknitter.org/export';
opts.exportUrl = opts.exportUrl || 'http//export.mapknitter.org/';

opts.fetchStatusUrl(opts);
}.bind(this));

};

L.setOptions(this, options);

L.distortableImage.group_action_map.Escape = '_decollectAll';

},

addHooks: function() {
Expand Down Expand Up @@ -2942,75 +3018,6 @@ L.DistortableCollection.Edit = L.Handler.extend({
if (e) { L.DomEvent.stopPropagation(e); }
},

startExport: function(opts) {
return new Promise(function(resolve) {
opts = opts || {};
opts.collection = opts.collection || this._group.generateExportJson();
opts.frequency = opts.frequency || 3000;
opts.scale = opts.scale || 100; // switch it to _getAvgCmPerPixel !
var statusUrl;
var updateInterval;

// this may be overridden to update the UI to show export progress or completion
// eslint-disable-next-line require-jsdoc
function _defaultUpdater(data) {
data = JSON.parse(data);
// optimization: fetch status directly from google storage:
if (statusUrl !== data.status_url && data.status_url.match('.json')) {
statusUrl = data.status_url;
}
if (data.status === 'complete') {
clearInterval(updateInterval);
resolve();
}
if (data.status === 'complete' && data.jpg !== null) {
alert('Export succeeded. http://export.mapknitter.org/' + data.jpg);
}
// TODO: update to clearInterval when status == "failed" if we update that in this file:
// https://github.com/publiclab/mapknitter-exporter/blob/main/lib/mapknitterExporter.rb
console.log(data);
}

// receives the URL of status.json, and starts running the updater to repeatedly fetch from status.json;
// this may be overridden to integrate with any UI
// eslint-disable-next-line require-jsdoc
function _defaultHandleStatusUrl(data) {
console.log(data);
statusUrl = '//export.mapknitter.org' + data;
opts.updater = opts.updater || _defaultUpdater;

// repeatedly fetch the status.json
updateInterval = setInterval(function intervalUpdater() {
$.ajax(statusUrl + '?' + Date.now(), {
// bust cache with timestamp
type: 'GET',
crossDomain: true,
}).done(function(data) {
opts.updater(data);
});
}, opts.frequency);
}

// eslint-disable-next-line require-jsdoc
function _fetchStatusUrl(collection, scale) {
opts.handleStatusUrl = opts.handleStatusUrl || _defaultHandleStatusUrl;

$.ajax({
url: '//export.mapknitter.org/export',
crossDomain: true,
type: 'POST',
data: {
collection: JSON.stringify(collection.images),
scale: scale,
},
success: opts.handleStatusUrl, // this handles the initial response
});
}

_fetchStatusUrl(opts.collection, opts.scale);
}.bind(this));
},

_addToolbar: function() {
var group = this._group;
var map = group._map;
Expand Down
158 changes: 158 additions & 0 deletions examples/export.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Leaflet.DistortableImage Example</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0, user-scalable=no"/>
<meta http-equiv="x-ua-compatible" content="ie=edge"/>

<script src="../node_modules/leaflet/dist/leaflet-src.js" type="text/javascript" charset="utf-8"></script>
<link rel="stylesheet" href="../node_modules/leaflet/dist/leaflet.css" media="screen" title="no title">
<script src="../node_modules/leaflet-toolbar/dist/leaflet.toolbar.js"></script>
<link href="../node_modules/leaflet-toolbar/dist/leaflet.toolbar.css" rel="stylesheet">

<!-- for full-res export -->
<script src="../node_modules/jquery/dist/jquery.js"></script>
<script defer src="../node_modules/webgl-distort/dist/webgl-distort.js"></script>
<script defer src="../node_modules/glfx/glfx.js"></script>

<!-- for EXIF geocode -->
<script defer type="text/javascript" src="../node_modules/exif-js/exif.js"></script>

<link rel="stylesheet" href="../dist/leaflet.distortableimage.css" media="screen" title="no title">
<script src="../dist/leaflet.distortableimage.js"></script>
</head>
<body style="margin:0;">

<form id="test_form" >
<input type="file" class="ldi" id="inputimage" accept="image/*">
</form>

<div id="map" style="width:100%; height:100%; position:absolute; top:0;"></div>

<script>

var map

(function(){

map = L.map('map').setView([51.505, -0.13], 13);
map.addGoogleMutant();

map.whenReady(function() {

img = L.distortableImageOverlay('example.jpg', {
corners: [
L.latLng(51.52, -0.14),
L.latLng(51.52,-0.10),
L.latLng(51.50, -0.14),
L.latLng(51.50,-0.10)
],
mode: 'lock',
});

// create a second image
img2 = L.distortableImageOverlay('example.jpg', {
corners: [
L.latLng(51.51, -0.20),
L.latLng(51.51,-0.16),
L.latLng(51.49, -0.21),
L.latLng(51.49,-0.17)
],
mode: 'freeRotate',
suppressToolbar: true,
});

var json = [{"nodes":[
{"lat":"41.8200378187","lon":"-71.4034409085"},
{"lat":"41.8199873593","lon":"-71.4030021564"},
{"lat":"41.8196229772","lon":"-71.4029728831"},
{"lat":"41.8198214546","lon":"-71.4034614433"}
],
"cm_per_pixel":23.0934,
"src":"https://s3.amazonaws.com/grassrootsmapping/warpables/312455/test.png"},
{"nodes":[
{"lat":"41.819898342","lon":"-71.4035387139"},
{"lat":"41.819898342","lon":"-71.4028493862"},
{"lat":"41.8195005594","lon":"-71.4028493862"},
{"lat":"41.8195005594","lon":"-71.4035387139"}
],
"cm_per_pixel":35.8578,
"src":"https://s3.amazonaws.com/grassrootsmapping/warpables/320983/test.png"}
];

// customize the function that starts up the export
function fetchStatusUrl(opts) {
$.ajax({
url: opts.exportStartUrl,
crossDomain: true,
type: 'POST',
data: {
collection: opts.collection,
scale: prompt("Choose a scale or use the default (cm per pixel):", 100) || opts.scale,
upload: true
},
success: function(data) { opts.handleStatusResponse(data, opts) }, // this handles the initial response
});
}

// receives the URL of status.json, and starts running the updater to repeatedly fetch from status.json;
// this may be overridden to integrate with any UI
function handleStatusResponse(data, opts) {
console.log(data);
var statusUrl = data.split('please visit, ')[1];

/* if we are getting status updates:
// repeatedly fetch the status.json
var updateInterval = setInterval(function intervalUpdater() {
$.ajax(statusUrl + '?' + Date.now(), {// bust cache with timestamp
type: 'GET',
crossDomain: true,
}).done(function(data) {
// do something with the response?
opts.updater(data);
});
}, opts.frequency);
*/

// but in this example, we're not; we just get the URL of the finished image;
// we should stop the spinner
opts.resolve();
// and we should initiate the download?
window.location = statusUrl;
}


// initialize the collection:
imgGroup = L.distortableCollection({
collection: json, // here we override the image data sent with a custom set
fetchStatusUrl: fetchStatusUrl,
handleStatusResponse: handleStatusResponse,
exportUrl: 'http://34.74.118.242/api/v2/export/', // used to
exportStartUrl: 'http://34.74.118.242/api/v2/export/' // used to initiate the export

// From this alternative exporter, we'll get a response like:
// "Your Image is exporting, to load Image please visit, http://34.74.118.242/api/v2/status/?pid=3d8233faa2ade0f0cee400fba1170890-7153"
// So, we can splice like this: response.split("please visit, ")[1]
// and get http://34.74.118.242/api/v2/status/?pid=3d8233faa2ade0f0cee400fba1170890-7153
// Noting, however, later we will expect to get a full Google Cloud Storage URL.

// remaining defaults are as follows, in /src/edit/DistortableCollection.Edit.js:
// collection = opts.collection || this._group.generateExportJson();
// frequency = opts.frequency || 3000;
// scale = opts.scale || 100; // switch it to _getAvgCmPerPixel !
// updater: function(json) {} // a function to handle the result of repeated fetching of the status.json file
// handleStatusResponse = opts.handleStatusResponse || _defaultHandleStatusResponse;
// fetchStatusUrl = opts.fetchStatusUrl || _defaultFetchStatusUrl;
// exportUrl = opts.exportUrl || 'http//export.mapknitter.org/';
// exportStartUrl = opts.exportStartUrl || '//export.mapknitter.org/export';

}).addTo(map);

imgGroup.addLayer(img);
imgGroup.addLayer(img2);
});

})();
</script>
</html>
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "leaflet-distortableimage",
"version": "0.9.0",
"version": "0.10.0",
"description": "Leaflet plugin enabling image overlays to be distorted, stretched, and warped (built for Public Lab's MapKnitter: http://publiclab.org).",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
Expand Down Expand Up @@ -42,7 +42,7 @@
"eslint": "^6.4.0",
"eslint-config-google": "^0.14.0",
"grunt": "^1.0.3",
"grunt-cli": "^1.2.0",
"grunt-cli": "^1.3.2",
"grunt-contrib-concat": "^1.0.1",
"grunt-contrib-jshint": "^2.0.0",
"grunt-contrib-watch": "^1.0.0",
Expand Down
Loading

0 comments on commit f1c260d

Please sign in to comment.