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

[WIP] customizable export UI for DistortableCollection #485

Merged
merged 18 commits into from
Jan 10, 2020
Merged
Show file tree
Hide file tree
Changes from 9 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
153 changes: 79 additions & 74 deletions dist/leaflet.distortableimage.js
Original file line number Diff line number Diff line change
Expand Up @@ -1643,7 +1643,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 @@ -2617,9 +2616,84 @@ L.DistortableCollection.Edit = L.Handler.extend({

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

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

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/';

// 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);
}
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.fetchStatusUrl(opts);
}.bind(this));

}

L.setOptions(this, options);

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

},

addHooks: function() {
Expand All @@ -2638,7 +2712,7 @@ L.DistortableCollection.Edit = L.Handler.extend({
singleclickon: this._singleClickListeners,
singleclickoff: this._resetClickListeners,
singleclick: this._singleClick,
boxcollectend: this._addCollections,
boxzoomend: this._addCollections,
}, this);

this._group.editable = true;
Expand All @@ -2661,7 +2735,7 @@ L.DistortableCollection.Edit = L.Handler.extend({
singleclickon: this._singleClickListeners,
singleclickoff: this._resetClickListeners,
singleclick: this._singleClick,
boxcollectend: this._addCollections,
boxzoomend: this._addCollections,
}, this);

this._decollectAll();
Expand Down Expand Up @@ -2717,7 +2791,7 @@ L.DistortableCollection.Edit = L.Handler.extend({

if (e) { oe = e.originalEvent; }
/**
* prevents image deselection following the 'boxcollectend' event - note 'shift' must not be released until dragging is complete
* prevents image deselection following the 'boxzoomend' event - note 'shift' must not be released until dragging is complete
* also prevents deselection following a click on a disabled img by differentiating it from the map
*/
if (oe && (oe.shiftKey || oe.target instanceof HTMLImageElement)) {
Expand Down Expand Up @@ -2761,7 +2835,7 @@ L.DistortableCollection.Edit = L.Handler.extend({
},

_addCollections: function(e) {
var box = e.boxCollectBounds;
var box = e.boxZoomBounds;
var map = this._group._map;

this._group.eachLayer(function(layer) {
Expand Down Expand Up @@ -2802,75 +2876,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
159 changes: 159 additions & 0 deletions examples/export.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<!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.handleStatusUrl(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

// 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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here I'm showing one reason WHY we want to be able to customize; we have alternative export servers with slightly different requirements, which we'd like to be able to connect to the Exporter in a simple standardized way.

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>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"eslint": "^6.4.0",
"eslint-config-google": "^0.13.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