From 87bee25a9c8c74fc5b9dae414794dc494772b621 Mon Sep 17 00:00:00 2001 From: MauricioVidulin Date: Fri, 4 Jul 2014 12:04:46 +0200 Subject: [PATCH 01/13] change field from polygon to multipolygon in ImageryReqest model --- ...l_field_imageryrequest_area_of_interest.py | 93 ++++++++++++++++++ ...d_field_imageryrequest_area_of_interest.py | 94 +++++++++++++++++++ django_project/imagery_requests/models.py | 2 +- 3 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 django_project/imagery_requests/migrations/0002_auto__del_field_imageryrequest_area_of_interest.py create mode 100644 django_project/imagery_requests/migrations/0003_auto__add_field_imageryrequest_area_of_interest.py diff --git a/django_project/imagery_requests/migrations/0002_auto__del_field_imageryrequest_area_of_interest.py b/django_project/imagery_requests/migrations/0002_auto__del_field_imageryrequest_area_of_interest.py new file mode 100644 index 0000000..0553ece --- /dev/null +++ b/django_project/imagery_requests/migrations/0002_auto__del_field_imageryrequest_area_of_interest.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Deleting field 'ImageryRequest.area_of_interest' + db.delete_column(u'imagery_requests_imageryrequest', 'area_of_interest') + + + def backwards(self, orm): + # Adding field 'ImageryRequest.area_of_interest' + db.add_column(u'imagery_requests_imageryrequest', 'area_of_interest', + self.gf('django.contrib.gis.db.models.fields.PolygonField')(default=1), + keep_default=False) + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'imagery_requests.customuser': { + 'Meta': {'object_name': 'CustomUser'}, + 'address': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'phone': ('django.db.models.fields.CharField', [], {'max_length': '15'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'imagery_requests.imageryrequest': { + 'Meta': {'object_name': 'ImageryRequest'}, + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created'", 'to': u"orm['imagery_requests.CustomUser']"}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'question_set': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['questions.QuestionSet']", 'null': 'True'}), + 'request_lead': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'request_lead'", 'null': 'True', 'to': u"orm['imagery_requests.CustomUser']"}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'default': '1', 'to': u"orm['imagery_requests.RequestStatus']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'imagery_requests.requestdate': { + 'Meta': {'object_name': 'RequestDate'}, + 'date': ('django.db.models.fields.DateField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'imagery_request': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['imagery_requests.ImageryRequest']"}), + 'time': ('django.db.models.fields.TimeField', [], {'default': 'datetime.time(0, 0)', 'null': 'True', 'blank': 'True'}) + }, + u'imagery_requests.requeststatus': { + 'Meta': {'object_name': 'RequestStatus'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '15'}) + }, + u'questions.question': { + 'Meta': {'object_name': 'Question'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}) + }, + u'questions.questionset': { + 'Meta': {'object_name': 'QuestionSet'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'questions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['questions.Question']", 'symmetrical': 'False'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '30'}) + } + } + + complete_apps = ['imagery_requests'] \ No newline at end of file diff --git a/django_project/imagery_requests/migrations/0003_auto__add_field_imageryrequest_area_of_interest.py b/django_project/imagery_requests/migrations/0003_auto__add_field_imageryrequest_area_of_interest.py new file mode 100644 index 0000000..8db8a0e --- /dev/null +++ b/django_project/imagery_requests/migrations/0003_auto__add_field_imageryrequest_area_of_interest.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'ImageryRequest.area_of_interest' + db.add_column(u'imagery_requests_imageryrequest', 'area_of_interest', + self.gf('django.contrib.gis.db.models.fields.MultiPolygonField')(), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'ImageryRequest.area_of_interest' + db.delete_column(u'imagery_requests_imageryrequest', 'area_of_interest') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + u'imagery_requests.customuser': { + 'Meta': {'object_name': 'CustomUser'}, + 'address': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'phone': ('django.db.models.fields.CharField', [], {'max_length': '15'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'imagery_requests.imageryrequest': { + 'Meta': {'object_name': 'ImageryRequest'}, + 'area_of_interest': ('django.contrib.gis.db.models.fields.MultiPolygonField', [], {}), + 'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'created'", 'to': u"orm['imagery_requests.CustomUser']"}), + 'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'question_set': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['questions.QuestionSet']", 'null': 'True'}), + 'request_lead': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'request_lead'", 'null': 'True', 'to': u"orm['imagery_requests.CustomUser']"}), + 'status': ('django.db.models.fields.related.ForeignKey', [], {'default': '1', 'to': u"orm['imagery_requests.RequestStatus']"}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'imagery_requests.requestdate': { + 'Meta': {'object_name': 'RequestDate'}, + 'date': ('django.db.models.fields.DateField', [], {}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'imagery_request': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['imagery_requests.ImageryRequest']"}), + 'time': ('django.db.models.fields.TimeField', [], {'default': 'datetime.time(0, 0)', 'null': 'True', 'blank': 'True'}) + }, + u'imagery_requests.requeststatus': { + 'Meta': {'object_name': 'RequestStatus'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '15'}) + }, + u'questions.question': { + 'Meta': {'object_name': 'Question'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'text': ('django.db.models.fields.TextField', [], {}) + }, + u'questions.questionset': { + 'Meta': {'object_name': 'QuestionSet'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'questions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['questions.Question']", 'symmetrical': 'False'}), + 'title': ('django.db.models.fields.CharField', [], {'max_length': '30'}) + } + } + + complete_apps = ['imagery_requests'] \ No newline at end of file diff --git a/django_project/imagery_requests/models.py b/django_project/imagery_requests/models.py index 667956d..f007578 100755 --- a/django_project/imagery_requests/models.py +++ b/django_project/imagery_requests/models.py @@ -116,7 +116,7 @@ class ImageryRequest(TimeStampedModelMixin, models.Model): created_by = models.ForeignKey( settings.AUTH_USER_MODEL, related_name='created' ) - area_of_interest = models.PolygonField( + area_of_interest = models.MultiPolygonField( srid=4326, help_text='Imagery request area of interest' ) status = models.ForeignKey( From a1d899590d186aaa2b6df9a2e2e9a4dbdc99cd01 Mon Sep 17 00:00:00 2001 From: MauricioVidulin Date: Tue, 8 Jul 2014 16:46:38 +0200 Subject: [PATCH 02/13] add support for drawing multipolygon - basics #88 --- .../templates/request_form.html | 112 +++++++++++++++++- 1 file changed, 109 insertions(+), 3 deletions(-) diff --git a/django_project/imagery_requests/templates/request_form.html b/django_project/imagery_requests/templates/request_form.html index 00b503e..6f5692d 100644 --- a/django_project/imagery_requests/templates/request_form.html +++ b/django_project/imagery_requests/templates/request_form.html @@ -150,21 +150,35 @@ }); map.addControl(drawControl); + // Initialise line util. + var lineUtil = L.LineUtil; + // Signal if drawing is activated. Initially it's not active. + var drawIsActive = false; + + map.on('draw:drawstart', function (e) { + drawIsActive = true; + }); + map.on('draw:created', function (e) { var type = e.layerType; var layer = e.layer; + drawIsActive = false; // delete previous layer - drawnItems.clearLayers(); + // drawnItems.clearLayers(); drawnItems.addLayer(layer); // dump to textarea $('#id_area_of_interest').val(JSON.stringify(layer.toGeoJSON()['geometry'])); + + // Set previuos_point var to null because next polygon won't have + // previous_point immediately. + previous_point = null; }); map.on('draw:drawstop', function (e) { - var type = e.layerType; + drawIsActive = false; }); map.on('draw:edited', function (e) { @@ -181,7 +195,99 @@ }); }); - //map.fitBounds(drawnItems.getBounds()); + var previous_point = null; + var current_point = null; + + map.on('click', function(e) { + // If drawing is activated check if new point is inside existing polygon or + // new line segment intersects with any of the existing line segments. + if (drawIsActive == true) { + current_point = [e.layerPoint.x, e.layerPoint.y]; + var polygons = []; + var layers = drawnItems.getLayers(); + + // If other polygons exist then go through the checking process. + if (layers.length > 0) { + // For each layer (polygon) get it's points coords. in pixels. + jQuery.each(layers, function () { + var polygon_points = this._originalPoints; + var clean_polygon_points = []; + + jQuery.each(polygon_points, function() { + clean_polygon_points.push([this.x, this.y]); + }); + + polygons.push(clean_polygon_points); + }); + + // Check if current point is inside existing polygon, + // (important only for the first point of polygon). + // For each polygon check if current line segment intersects + // any line segement of the polygon. + jQuery.each(polygons, function() { + // If previous_point exists (means that line segment exists) + // check intersections. + if (previous_point) { + p1 = {x: previous_point[0], y: previous_point[1]}; + p2 = {x: current_point[0], y: current_point[1]}; + + for (i=0, j=this.length-1;i y) != (yj > y)) + && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + if (intersect) inside = !inside; + } + return inside; + } {% endblock %} \ No newline at end of file From 1f820eedf5b9296a60a362147fea67104e5a53c5 Mon Sep 17 00:00:00 2001 From: MauricioVidulin Date: Wed, 9 Jul 2014 19:22:29 +0200 Subject: [PATCH 03/13] add checking process after drawing polygon #88 --- .../templates/request_form.html | 118 ++++++++++++++---- 1 file changed, 95 insertions(+), 23 deletions(-) diff --git a/django_project/imagery_requests/templates/request_form.html b/django_project/imagery_requests/templates/request_form.html index 6f5692d..9480df5 100644 --- a/django_project/imagery_requests/templates/request_form.html +++ b/django_project/imagery_requests/templates/request_form.html @@ -150,8 +150,6 @@ }); map.addControl(drawControl); - // Initialise line util. - var lineUtil = L.LineUtil; // Signal if drawing is activated. Initially it's not active. var drawIsActive = false; @@ -160,15 +158,41 @@ }); map.on('draw:created', function (e) { - var type = e.layerType; var layer = e.layer; drawIsActive = false; - // delete previous layer - // drawnItems.clearLayers(); - drawnItems.addLayer(layer); + // Layers list. + var layers = drawnItems.getLayers(); + + // Check after drawing: line segments intersection; point in another polygon; + // polygon overlaps another polygon. + // Important because sometimes checks during drawing are not made. + // It can happen because sometimes map.on('click') event listener doesn't + // "get" that click (e.g., when you draw points on map quickly). + if (layers.length > 1) { + + var polygons = get_cleaned_polygons(layers); + var trouble_polygons = check_after_drawing(polygons) + + if (trouble_polygons.length > 0) { + var polygon_counter = 0; + + for (var key in drawnItems._layers) { + if($.inArray(polygon_counter, trouble_polygons) != -1) { + drawnItems._layers[key].setStyle({ + fillColor: '#ca4c4d', + color: '#ca4c4d' + }); + + } + + polygon_counter++ + } + } + } + // dump to textarea $('#id_area_of_interest').val(JSON.stringify(layer.toGeoJSON()['geometry'])); @@ -182,7 +206,8 @@ }); map.on('draw:edited', function (e) { - var layers = e.layers; + layers = e.layers; + layers.eachLayer(function (layer) { $('#id_area_of_interest').val(JSON.stringify(layer.toGeoJSON()['geometry'])); }); @@ -203,22 +228,11 @@ // new line segment intersects with any of the existing line segments. if (drawIsActive == true) { current_point = [e.layerPoint.x, e.layerPoint.y]; - var polygons = []; var layers = drawnItems.getLayers(); // If other polygons exist then go through the checking process. if (layers.length > 0) { - // For each layer (polygon) get it's points coords. in pixels. - jQuery.each(layers, function () { - var polygon_points = this._originalPoints; - var clean_polygon_points = []; - - jQuery.each(polygon_points, function() { - clean_polygon_points.push([this.x, this.y]); - }); - - polygons.push(clean_polygon_points); - }); + polygons = get_cleaned_polygons(layers) // Check if current point is inside existing polygon, // (important only for the first point of polygon). @@ -228,14 +242,14 @@ // If previous_point exists (means that line segment exists) // check intersections. if (previous_point) { - p1 = {x: previous_point[0], y: previous_point[1]}; - p2 = {x: current_point[0], y: current_point[1]}; + var p1 = {x: previous_point[0], y: previous_point[1]}; + var p2 = {x: current_point[0], y: current_point[1]}; for (i=0, j=this.length-1;i Date: Wed, 9 Jul 2014 19:29:17 +0200 Subject: [PATCH 04/13] add checking process after editing #88 --- .../templates/request_form.html | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/django_project/imagery_requests/templates/request_form.html b/django_project/imagery_requests/templates/request_form.html index 9480df5..e3b7fed 100644 --- a/django_project/imagery_requests/templates/request_form.html +++ b/django_project/imagery_requests/templates/request_form.html @@ -206,11 +206,41 @@ }); map.on('draw:edited', function (e) { - layers = e.layers; + // Layers list. + var layers = drawnItems.getLayers(); - layers.eachLayer(function (layer) { - $('#id_area_of_interest').val(JSON.stringify(layer.toGeoJSON()['geometry'])); - }); + if (layers.length > 1) { + var polygons = get_cleaned_polygons(layers); + var trouble_polygons = check_after_drawing(polygons) + + // Set default color to every polygon (assume they are correct). + for (var key in drawnItems._layers) { + drawnItems._layers[key].setStyle({ + fillColor: '#FFA500', + color: '#FFA500' + }); + } + + // Set "error" color to trouble polygons. + if (trouble_polygons.length > 0) { + var polygon_counter = 0; + + for (var key in drawnItems._layers) { + if($.inArray(polygon_counter, trouble_polygons) != -1) { + drawnItems._layers[key].setStyle({ + fillColor: '#ca4c4d', + color: '#ca4c4d' + }); + } + + polygon_counter++ + } + } + } + + // layers.eachLayer(function (layer) { + // $('#id_area_of_interest').val(JSON.stringify(layer.toGeoJSON()['geometry'])); + // }); }); map.on('draw:deleted', function (e) { From c624cdd555f2bae2bbacb2d167e2ad18f11e18e9 Mon Sep 17 00:00:00 2001 From: MauricioVidulin Date: Thu, 10 Jul 2014 11:21:03 +0200 Subject: [PATCH 05/13] fix checking process after editing #88 --- .../templates/request_form.html | 59 +++++++++++-------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/django_project/imagery_requests/templates/request_form.html b/django_project/imagery_requests/templates/request_form.html index e3b7fed..9249a33 100644 --- a/django_project/imagery_requests/templates/request_form.html +++ b/django_project/imagery_requests/templates/request_form.html @@ -152,6 +152,8 @@ // Signal if drawing is activated. Initially it's not active. var drawIsActive = false; + // Signal if edition is saved. Used for draw:editstop event. + var isEditionSaved = false; map.on('draw:drawstart', function (e) { drawIsActive = true; @@ -160,6 +162,7 @@ map.on('draw:created', function (e) { var layer = e.layer; drawIsActive = false; + console.log('nacrtano') drawnItems.addLayer(layer); @@ -174,7 +177,7 @@ if (layers.length > 1) { var polygons = get_cleaned_polygons(layers); - var trouble_polygons = check_after_drawing(polygons) + var trouble_polygons = check_after_drawing(polygons); if (trouble_polygons.length > 0) { var polygon_counter = 0; @@ -185,10 +188,8 @@ fillColor: '#ca4c4d', color: '#ca4c4d' }); - } - - polygon_counter++ + polygon_counter++; } } } @@ -206,23 +207,27 @@ }); map.on('draw:edited', function (e) { - // Layers list. - var layers = drawnItems.getLayers(); + // draw:edited gets called after saving edition, BUT before draw:editstop + //(i.e. draw:editstop is called after both save and cancel edit). + // So our custom logic that we want to implement after saving edition + // should go in draw:editstop. - if (layers.length > 1) { - var polygons = get_cleaned_polygons(layers); - var trouble_polygons = check_after_drawing(polygons) + isEditionSaved = true; + }); - // Set default color to every polygon (assume they are correct). - for (var key in drawnItems._layers) { - drawnItems._layers[key].setStyle({ - fillColor: '#FFA500', - color: '#FFA500' - }); - } + map.on('draw:editstop', function (e) { + // If edition is saved do the checking process, + // if not (i.e. canceled ) just pass. + if (isEditionSaved == true) { + // Layers list. + var layers = drawnItems.getLayers(); - // Set "error" color to trouble polygons. - if (trouble_polygons.length > 0) { + if (layers.length > 1) { + var polygons = get_cleaned_polygons(layers); + var trouble_polygons = check_after_drawing(polygons); + + // Set "error" color to trouble polygons and default color to + // regular polygons. var polygon_counter = 0; for (var key in drawnItems._layers) { @@ -231,16 +236,22 @@ fillColor: '#ca4c4d', color: '#ca4c4d' }); + } else { + // Set default color. + drawnItems._layers[key].setStyle({ + fillColor: '#FFA500', + color: '#FFA500' + }); } - - polygon_counter++ + polygon_counter++; } } - } - // layers.eachLayer(function (layer) { - // $('#id_area_of_interest').val(JSON.stringify(layer.toGeoJSON()['geometry'])); - // }); + // layers.eachLayer(function (layer) { + // $('#id_area_of_interest').val(JSON.stringify(layer.toGeoJSON()['geometry'])); + // }); + } + isEditionSaved = false; }); map.on('draw:deleted', function (e) { From 408613d0259c26c80bfeb180fca212f5c7c1ab6e Mon Sep 17 00:00:00 2001 From: MauricioVidulin Date: Thu, 10 Jul 2014 11:42:34 +0200 Subject: [PATCH 06/13] add checking process after delete #88 --- .../templates/request_form.html | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/django_project/imagery_requests/templates/request_form.html b/django_project/imagery_requests/templates/request_form.html index 9249a33..5b95ae0 100644 --- a/django_project/imagery_requests/templates/request_form.html +++ b/django_project/imagery_requests/templates/request_form.html @@ -162,7 +162,6 @@ map.on('draw:created', function (e) { var layer = e.layer; drawIsActive = false; - console.log('nacrtano') drawnItems.addLayer(layer); @@ -255,10 +254,46 @@ }); map.on('draw:deleted', function (e) { - var layers = e.layers; - layers.eachLayer(function (layer) { - $('#id_area_of_interest').val(''); - }); + // var layers = e.layers; + // layers.eachLayer(function (layer) { + // $('#id_area_of_interest').val(''); + // }); + + // Layers list. + var layers = drawnItems.getLayers(); + + if (layers.length > 1) { + var polygons = get_cleaned_polygons(layers); + var trouble_polygons = check_after_drawing(polygons); + + // Set "error" color to trouble polygons and default color to + // regular polygons. + var polygon_counter = 0; + + for (var key in drawnItems._layers) { + if($.inArray(polygon_counter, trouble_polygons) != -1) { + drawnItems._layers[key].setStyle({ + fillColor: '#ca4c4d', + color: '#ca4c4d' + }); + } else { + // Set default color. + drawnItems._layers[key].setStyle({ + fillColor: '#FFA500', + color: '#FFA500' + }); + } + polygon_counter++; + } + } else { + // Set default color to only polygon left. + for (var key in drawnItems._layers) { + drawnItems._layers[key].setStyle({ + fillColor: '#FFA500', + color: '#FFA500' + }); + } + } }); var previous_point = null; From cf3bfbdb8f86947daf46aa3656a2a21bd38db482 Mon Sep 17 00:00:00 2001 From: MauricioVidulin Date: Thu, 10 Jul 2014 15:09:28 +0200 Subject: [PATCH 07/13] enhance polygon checking process #88 --- .../templates/request_form.html | 239 +++++++++++++----- 1 file changed, 171 insertions(+), 68 deletions(-) diff --git a/django_project/imagery_requests/templates/request_form.html b/django_project/imagery_requests/templates/request_form.html index 5b95ae0..62dfe86 100644 --- a/django_project/imagery_requests/templates/request_form.html +++ b/django_project/imagery_requests/templates/request_form.html @@ -176,7 +176,10 @@ if (layers.length > 1) { var polygons = get_cleaned_polygons(layers); - var trouble_polygons = check_after_drawing(polygons); + + var result = check_after_drawing(polygons); + var trouble_polygons = result.trouble_polygons; + var error_messages = result.error_messages; if (trouble_polygons.length > 0) { var polygon_counter = 0; @@ -220,36 +223,38 @@ if (isEditionSaved == true) { // Layers list. var layers = drawnItems.getLayers(); + var polygons = get_cleaned_polygons(layers); - if (layers.length > 1) { - var polygons = get_cleaned_polygons(layers); - var trouble_polygons = check_after_drawing(polygons); + var result = check_after_drawing(polygons); + var trouble_polygons = result.trouble_polygons; + var error_messages = result.error_messages; - // Set "error" color to trouble polygons and default color to - // regular polygons. - var polygon_counter = 0; + // Set "error" color to trouble polygons and default color to + // regular polygons. + var polygon_counter = 0; - for (var key in drawnItems._layers) { - if($.inArray(polygon_counter, trouble_polygons) != -1) { - drawnItems._layers[key].setStyle({ - fillColor: '#ca4c4d', - color: '#ca4c4d' - }); - } else { - // Set default color. - drawnItems._layers[key].setStyle({ - fillColor: '#FFA500', - color: '#FFA500' - }); - } - polygon_counter++; + for (var key in drawnItems._layers) { + if($.inArray(polygon_counter, trouble_polygons) != -1) { + drawnItems._layers[key].setStyle({ + fillColor: '#ca4c4d', + color: '#ca4c4d' + }); + } else { + // Set default color. + drawnItems._layers[key].setStyle({ + fillColor: '#FFA500', + color: '#FFA500' + }); } + + polygon_counter++; } // layers.eachLayer(function (layer) { // $('#id_area_of_interest').val(JSON.stringify(layer.toGeoJSON()['geometry'])); // }); } + isEditionSaved = false; }); @@ -261,38 +266,31 @@ // Layers list. var layers = drawnItems.getLayers(); + var polygons = get_cleaned_polygons(layers); - if (layers.length > 1) { - var polygons = get_cleaned_polygons(layers); - var trouble_polygons = check_after_drawing(polygons); + var result = check_after_drawing(polygons); + var trouble_polygons = result.trouble_polygons; + var error_messages = result.error_messages; - // Set "error" color to trouble polygons and default color to - // regular polygons. - var polygon_counter = 0; + // Set "error" color to trouble polygons and default color to + // regular polygons. + var polygon_counter = 0; - for (var key in drawnItems._layers) { - if($.inArray(polygon_counter, trouble_polygons) != -1) { - drawnItems._layers[key].setStyle({ - fillColor: '#ca4c4d', - color: '#ca4c4d' - }); - } else { - // Set default color. - drawnItems._layers[key].setStyle({ - fillColor: '#FFA500', - color: '#FFA500' - }); - } - polygon_counter++; - } - } else { - // Set default color to only polygon left. - for (var key in drawnItems._layers) { + for (var key in drawnItems._layers) { + if($.inArray(polygon_counter, trouble_polygons) != -1) { + drawnItems._layers[key].setStyle({ + fillColor: '#ca4c4d', + color: '#ca4c4d' + }); + } else { + // Set default color. drawnItems._layers[key].setStyle({ fillColor: '#FFA500', color: '#FFA500' - }); + }); } + + polygon_counter++; } }); @@ -365,42 +363,147 @@ function check_after_drawing(polygons) { var trouble_polygons = []; + var error_messages = [] - // Check if any line segemnt of any polygon intersects with any line - // segment of any other polygon. - for (var i = 0, j = polygons.length - 1; i < polygons.length; j = i++) { - var poly_a = polygons[i]; - var poly_b = polygons[j]; + // Check if any polygon intersects any other polygon. + // Check if any polygon covers any other polygon. + // Check if polygon intersects itself. Leaflet Draw plugin does that + // check during drawing, but not after polygons are edited. + jQuery.each(polygons, function(index_a, poly_a) { - for (var k = 0, h = poly_a.length - 1; k < poly_a.length; h = k++) { - var p_1a = {x: poly_a[k][0], y: poly_a[k][1]}; - var p_2a = {x: poly_a[h][0], y: poly_a[h][1]}; + jQuery.each(polygons, function(index_b, poly_b) { + + if (poly_b != poly_a) { + + var result = polygon_intersects_polygon( + error_messages, trouble_polygons, + poly_a, index_a, + poly_b, index_b); + + trouble_polygons = result.trouble_polygons; + error_messages = result.error_messages; + + var result = polygon_covers_polygon( + error_messages, trouble_polygons, + poly_a, index_a, + poly_b, index_b); + + trouble_polygons = result.trouble_polygons; + error_messages = result.error_messages; + } + }); + + var result = polygon_intersects_itself( + error_messages, trouble_polygons, + poly_a, index_a); + + trouble_polygons = result.trouble_polygons; + error_messages = result.error_messages; + }); + + return { + trouble_polygons: trouble_polygons, + error_messages: error_messages + } + } + + // Check if any line segment of any polygon intersects with any line + // segment of any other polygon. + function polygon_intersects_polygon(error_messages, trouble_polygons, poly_a, index_a, poly_b, index_b) { + var error_message = "Area shouldn't intersect with another area." + + for (var k = 0, h = poly_a.length - 1; k < poly_a.length; h = k++) { + var p_1a = {x: poly_a[k][0], y: poly_a[k][1]}; + var p_2a = {x: poly_a[h][0], y: poly_a[h][1]}; + + for (var e = 0, f = poly_b.length - 1; e < poly_b.length; f = e++) { + var p_1b = {x: poly_b[e][0], y: poly_b[e][1]}; + var p_2b = {x: poly_b[f][0], y: poly_b[f][1]}; + + var intersects = L.LineUtil.segmentsIntersect(p_1a, p_2a, p_1b, p_2b); + + if (intersects == true) { + if( $.inArray(index_a, trouble_polygons) == -1 ) { + trouble_polygons.push(index_a); + } + + if ($.inArray(index_b, trouble_polygons) == -1 ) { + trouble_polygons.push(index_b); + } - for (var e = 0, f = poly_b.length - 1; e < poly_b.length; f = e++) { - var p_1b = {x: poly_b[e][0], y: poly_b[e][1]}; - var p_2b = {x: poly_b[f][0], y: poly_b[f][1]}; + if ($.inArray(error_message, error_messages) == -1 ) { + error_messages.push(error_message); + } + } + } + } + + return { + trouble_polygons: trouble_polygons, + error_messages: error_messages + } + } + + // Check if any polygon covers any other polygon. We're actually checking + // if point from one polygon lies inside another polygon. + function polygon_covers_polygon(error_messages, trouble_polygons, poly_a, index_a, poly_b, index_b) { + var error_message = "Area shouldn't cover another area." + + jQuery.each(poly_a, function(point_index, point) { + var pointIsInPolygon = point_in_polygon(point, poly_b); + + if (pointIsInPolygon == true) { + if( $.inArray(index_a, trouble_polygons) == -1 ) { + trouble_polygons.push(index_a); + } + + if( $.inArray(index_b, trouble_polygons) == -1 ) { + trouble_polygons.push(index_b); + } - var intersects = L.LineUtil.segmentsIntersect(p_1a, p_2a, p_1b, p_2b); + if ($.inArray(error_message, error_messages) == -1 ) { + error_messages.push(error_message); + } + } + }); + + return { + trouble_polygons: trouble_polygons, + error_messages: error_messages + } + } + + function polygon_intersects_itself(error_messages, trouble_polygons, poly_a, index_a) { + var error_message = "Area shouldn't intersect with itself." + + for (var i = 0, j = poly_a.length - 1; i < poly_a.length; j = i++) { + var p_1 = {x: poly_a[i][0], y: poly_a[i][1]} + var p_2 = {x: poly_a[j][0], y: poly_a[j][1]} + + for (var k = 0, h = poly_a.length - 1; k < poly_a.length; h = k++) { + var p_3 = {x: poly_a[h][0], y: poly_a[h][1]} + var p_4 = {x: poly_a[k][0], y: poly_a[k][1]} + + if (p_3 != p_1 && p_4 != p_2) { + var intersects = L.LineUtil.segmentsIntersect(p_1, p_2, p_3, p_4); if (intersects == true) { - if( $.inArray(i, trouble_polygons) == -1 ) { - trouble_polygons.push(i); + if( $.inArray(index_a, trouble_polygons) == -1 ) { + trouble_polygons.push(index_a); } - if ($.inArray(j, trouble_polygons) == -1 ) { - trouble_polygons.push(j); + if ($.inArray(error_message, error_messages) == -1 ) { + error_messages.push(error_message); } } } } - // If there are only 2 polgons, break the loop. We don't need to - // do the process for 1st-2nd and 2nd-1st pair of polygons. - if (polygons.length == 2) { - break; - } } - return trouble_polygons; + return { + trouble_polygons: trouble_polygons, + error_messages: error_messages + } } function get_cleaned_polygons(layers) { From d7c65da85fda5dc2794d8888662df09d659f232e Mon Sep 17 00:00:00 2001 From: MauricioVidulin Date: Fri, 11 Jul 2014 15:01:38 +0200 Subject: [PATCH 08/13] add new version of leaflet-omnivore to parse multipolygon --- django_project/web/static/js/leaflet-omnivore.min.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/django_project/web/static/js/leaflet-omnivore.min.js b/django_project/web/static/js/leaflet-omnivore.min.js index 833297e..55229fd 100644 --- a/django_project/web/static/js/leaflet-omnivore.min.js +++ b/django_project/web/static/js/leaflet-omnivore.min.js @@ -1,3 +1 @@ -!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var o;"undefined"!=typeof window?o=window:"undefined"!=typeof global?o=global:"undefined"!=typeof self&&(o=self),o.omnivore=e()}}(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o0){var fn=queue.shift();fn()}}},true);return function nextTick(fn){queue.push(fn);window.postMessage("process-tick","*")}}return function nextTick(fn){setTimeout(fn,0)}}();process.title="browser";process.browser=true;process.env={};process.argv=[];process.binding=function(name){throw new Error("process.binding is not supported")};process.cwd=function(){return"/"};process.chdir=function(dir){throw new Error("process.chdir is not supported")}},{}],5:[function(_dereq_,module,exports){function xhr(url,callback,cors){var sent=false;if(typeof window.XMLHttpRequest==="undefined"){return callback(Error("Browser not supported"))}if(typeof cors==="undefined"){var m=url.match(/^\s*https?:\/\/[^\/]*/);cors=m&&m[0]!==location.protocol+"//"+location.domain+(location.port?":"+location.port:"")}var x;function isSuccessful(status){return status>=200&&status<300||status===304}if(cors&&(typeof window.XDomainRequest==="object"||typeof window.XDomainRequest==="function")){x=new window.XDomainRequest;var original=callback;callback=function(){if(sent){original.apply(this,arguments)}else{var that=this,args=arguments;setTimeout(function(){original.apply(that,args)},0)}}}else{x=new window.XMLHttpRequest}function loaded(){if(x.status===undefined||isSuccessful(x.status))callback.call(x,null,x);else callback.call(x,x,null)}if("onload"in x){x.onload=loaded}else{x.onreadystatechange=function readystate(){if(x.readyState===4){loaded()}}}x.onerror=function error(evt){callback.call(this,evt||true,null);callback=function(){}};x.onprogress=function(){};x.ontimeout=function(evt){callback.call(this,evt,null);callback=function(){}};x.onabort=function(evt){callback.call(this,evt,null);callback=function(){}};x.open("GET",url,true);x.send(null);sent=true;return x}if(typeof module!=="undefined")module.exports=xhr},{}],6:[function(_dereq_,module,exports){var dsv=_dereq_("dsv"),sexagesimal=_dereq_("sexagesimal");function isLat(f){return!!f.match(/(Lat)(itude)?/gi)}function isLon(f){return!!f.match(/(L)(on|ng)(gitude)?/i)}function keyCount(o){return typeof o=="object"?Object.keys(o).length:0}function autoDelimiter(x){var delimiters=[",",";"," ","|"];var results=[];delimiters.forEach(function(delimiter){var res=dsv(delimiter).parse(x);if(res.length>=1){var count=keyCount(res[0]);for(var i=0;i= N) return EOF; // special case: end of file\n if (eol) return eol = false, EOL; // special case: end of line\n\n // special case: quotes\n var j = I;\n if (text.charCodeAt(j) === 34) {\n var i = j;\n while (i++ < N) {\n if (text.charCodeAt(i) === 34) {\n if (text.charCodeAt(i + 1) !== 34) break;\n ++i;\n }\n }\n I = i + 2;\n var c = text.charCodeAt(i + 1);\n if (c === 13) {\n eol = true;\n if (text.charCodeAt(i + 2) === 10) ++I;\n } else if (c === 10) {\n eol = true;\n }\n return text.substring(j + 1, i).replace(/""/g, "\\"");\n }\n\n // common case: find next delimiter or newline\n while (I < N) {\n var c = text.charCodeAt(I++), k = 1;\n if (c === 10) eol = true; // \\n\n else if (c === 13) { eol = true; if (text.charCodeAt(I) === 10) ++I, ++k; } // \\r|\\r\\n\n else if (c !== delimiterCode) continue;\n return text.substring(j, I - k);\n }\n\n // special case: last token before EOF\n return text.substring(j);\n }\n\n while ((t = token()) !== EOF) {\n var a = [];\n while (t !== EOL && t !== EOF) {\n a.push(t);\n t = token();\n }\n if (f && !(a = f(a, n++))) continue;\n rows.push(a);\n }\n\n return rows;\n };\n\n dsv.format = function(rows) {\n if (Array.isArray(rows[0])) return dsv.formatRows(rows); // deprecated; use formatRows\n var fieldSet = {}, fields = [];\n\n // Compute unique fields in order of discovery.\n rows.forEach(function(row) {\n for (var field in row) {\n if (!(field in fieldSet)) {\n fields.push(fieldSet[field] = field);\n }\n }\n });\n\n return [fields.map(formatValue).join(delimiter)].concat(rows.map(function(row) {\n return fields.map(function(field) {\n return formatValue(row[field]);\n }).join(delimiter);\n })).join("\\n");\n };\n\n dsv.formatRows = function(rows) {\n return rows.map(formatRow).join("\\n");\n };\n\n function formatRow(row) {\n return row.map(formatValue).join(delimiter);\n }\n\n function formatValue(text) {\n return reFormat.test(text) ? "\\"" + text.replace(/\\"/g, "\\"\\"") + "\\"" : text;\n }\n\n return dsv;\n}\n'+";return dsv")()},{fs:2}],8:[function(_dereq_,module,exports){module.exports=function(x,dims){if(!dims)dims="NSEW";if(typeof x!=="string")return null;var r=/^([0-9.]+)°? *(?:([0-9.]+)['’′‘] *)?(?:([0-9.]+)(?:''|"|”|″) *)?([NSEW])?/,m=x.match(r);if(!m)return null;else if(m[4]&&dims.indexOf(m[4])===-1)return null;else return((m[1]?parseFloat(m[1]):0)+(m[2]?parseFloat(m[2])/60:0)+(m[3]?parseFloat(m[3])/3600:0))*(m[4]&&m[4]==="S"||m[4]==="W"?-1:1)}},{}],9:[function(_dereq_,module,exports){(function(process){toGeoJSON=function(){"use strict";var removeSpace=/\s*/g,trimSpace=/^\s*|\s*$/g,splitSpace=/\s+/;function okhash(x){if(!x||!x.length)return 0;for(var i=0,h=0;ix1)x1=x;if(yy1)y1=y}function boundLine(coordinates){coordinates.forEach(boundPoint)}function boundMultiLine(coordinates){coordinates.forEach(boundLine)}for(var key in objects){boundGeometry(objects[key])}return[x0,y0,x1,y1]}},{}],13:[function(_dereq_,module,exports){exports.name="cartesian";exports.formatDistance=formatDistance;exports.ringArea=ringArea;exports.absoluteArea=Math.abs;exports.triangleArea=triangleArea;exports.distance=distance;function formatDistance(d){return d.toString()}function ringArea(ring){var i=-1,n=ring.length,a,b=ring[n-1],area=0;while(++imax)max=area,best=i}if(best){t=rings[best],rings[best]=rings[0],rings[0]=t;t=areas[best],areas[best]=areas[0],areas[0]=t}if(areas[0]<0)reverse(rings[0]);for(var i=1;i0)reverse(rings[i])}}}function noop(){}},{"../../":10,"./coordinate-systems":16,"./type":39}],15:[function(_dereq_,module,exports){module.exports=function(objects,id){if(arguments.length<2)id=function(d){return d.id};function idObject(object){if(object&&idObjectType.hasOwnProperty(object.type))idObjectType[object.type](object)}function idFeature(feature){var i=id(feature);if(i==null)delete feature.id;else feature.id=i}var idObjectType={Feature:idFeature,FeatureCollection:function(collection){collection.features.forEach(idFeature)}};for(var key in objects){idObject(objects[key])}return objects}},{}],16:[function(_dereq_,module,exports){module.exports={cartesian:_dereq_("./cartesian"),spherical:_dereq_("./spherical")}},{"./cartesian":13,"./spherical":26}],17:[function(_dereq_,module,exports){module.exports=function(topology){var arcs=topology.arcs,i=-1,n=arcs.length;while(++i0))minimumArea=Number.MIN_VALUE;if(preserveAttached){var uniqueRingByArc={},ringIndex=0;var checkAttachment=type({LineString:noop,MultiLineString:noop,Point:noop,MultiPoint:noop,MultiPolygon:function(multiPolygon){var arcs=multiPolygon.arcs,i=-1,n=arcs.length;while(++i=0&&uniqueRing!==ringIndex)uniqueRingByArc[arc]=-1;else uniqueRingByArc[arc]=ringIndex}}}});preserveRing=function(ring){for(var j=0,m=ring.length;j=minimumArea}function filterInteriorRing(ring){return preserveRing(ring)||system.absoluteArea(-ringArea(ring))>=minimumArea}function ringArea(ring){return system.ringArea(topojson.feature(topology,{type:"Polygon",arcs:[ring]}).geometry.coordinates[0])}};function noop(){}function preserveNone(){return false}},{"../../":10,"./clockwise":14,"./coordinate-systems":16,"./prune":22,"./type":39}],19:[function(_dereq_,module,exports){module.exports=function(objects){function geomifyObject(object){return(object&&geomifyObjectType.hasOwnProperty(object.type)?geomifyObjectType[object.type]:geomifyGeometry)(object)}function geomifyFeature(feature){var geometry=feature.geometry;if(geometry==null){feature.type=null}else{geomifyGeometry(geometry);feature.type=geometry.type;if(geometry.geometries)feature.geometries=geometry.geometries;else if(geometry.coordinates)feature.coordinates=geometry.coordinates}delete feature.geometry;return feature}function geomifyGeometry(geometry){if(!geometry)return{type:null};if(geomifyGeometryType.hasOwnProperty(geometry.type))geomifyGeometryType[geometry.type](geometry);return geometry}var geomifyObjectType={Feature:geomifyFeature,FeatureCollection:function(collection){collection.type="GeometryCollection";collection.geometries=collection.features;collection.features.forEach(geomifyFeature);delete collection.features;return collection}};var geomifyGeometryType={GeometryCollection:function(o){var geometries=o.geometries,i=-1,n=geometries.length;while(++i=minimumArea){target=arc[++j];target[0]=source[0]+dx;target[1]=source[1]+dy;dx=dy=0}else{dx+=source[0];dy+=source[1]}}arc.length=++j}:function(arc){var i=-1,j=-1,n=arc.length,point;while(++i=minimumArea){arc[++j]=point}}arc.length=++j});topology.arcs.forEach(topology.transform?function(arc){var i=0,j=0,n=arc.length,p=arc[0];p.length=2;while(++i1?km.toFixed(3)+"km":(km*1e3).toPrecision(3)+"m")+" ("+k.toPrecision(3)+"°)"}function ringArea(ring){if(!ring.length)return 0;var area=0,p=ring[0],λ=p[0]*radians,φ=p[1]*radians/2+π_4,λ0=λ,cosφ0=Math.cos(φ),sinφ0=Math.sin(φ);for(var i=1,n=ring.length;iπ?area-2*π:area<-π?area+2*π:area)}function absoluteArea(a){return a<0?a+4*π:a}function triangleArea(t){var a=distance(t[0],t[1]),b=distance(t[1],t[2]),c=distance(t[2],t[0]),s=(a+b+c)/2;return 4*Math.atan(Math.sqrt(Math.max(0,Math.tan(s/2)*Math.tan((s-a)/2)*Math.tan((s-b)/2)*Math.tan((s-c)/2))))}function distance(a,b){var Δλ=(b[0]-a[0])*radians,sinΔλ=Math.sin(Δλ),cosΔλ=Math.cos(Δλ),sinφ0=Math.sin(a[1]*radians),cosφ0=Math.cos(a[1]*radians),sinφ1=Math.sin(b[1]*radians),cosφ1=Math.cos(b[1]*radians),_;return Math.atan2(Math.sqrt((_=cosφ1*sinΔλ)*_+(_=cosφ0*sinφ1-sinφ0*cosφ1*cosΔλ)*_),sinφ0*sinφ1+cosφ0*cosφ1*cosΔλ)}function haversinDistance(x0,y0,x1,y1){x0*=radians,y0*=radians,x1*=radians,y1*=radians;return 2*Math.asin(Math.sqrt(haversin(y1-y0)+Math.cos(y0)*Math.cos(y1)*haversin(x1-x0)))}function haversin(x){return(x=Math.sin(x/2))*x}},{}],27:[function(_dereq_,module,exports){var type=_dereq_("./type");module.exports=function(objects,transform){var ε=.01,x0=-180,x0e=x0+ε,x1=180,x1e=x1-ε,y0=-90,y0e=y0+ε,y1=90,y1e=y1-ε;if(transform){var kx=transform.scale[0],ky=transform.scale[1],dx=transform.translate[0],dy=transform.translate[1];x0=Math.round((x0-dx)/kx);x1=Math.round((x1-dx)/kx);y0=Math.round((y0-dy)/ky);y1=Math.round((y1-dy)/ky);x0e=Math.round((x0e-dx)/kx);x1e=Math.round((x1e-dx)/kx);y0e=Math.round((y0e-dy)/ky);y1e=Math.round((y1e-dy)/ky)}function normalizePoint(y){return y<=y0e?[0,y0]:y>=y1e?[0,y1]:[x0,y]}function stitchPolygons(polygons){var fragments=[];for(var p=0,np=polygons.length;p=x1e||y<=y0e||y>=y1e){for(var k=i+1;kx0e&&xky0e&&yk=n)break;fragments.push(ring=ring.slice(k-1));ring[0]=normalizePoint(ring[0][1]);ring.polygon=polygon;i=-1;n=ring.length}}}polygon.length=0}var fragmentByStart={},fragmentByEnd={};for(var i=0,n=fragments.length;i180+ε||bbox[3]>90+ε;if(!system){system=systems[oversize?"cartesian":"spherical"];if(options)options["coordinate-system"]=system.name}if(system===systems.spherical){if(oversize)throw new Error("spherical coordinates outside of [±180°, ±90°]");if(bbox[0]<-180+ε)bbox[0]=-180;if(bbox[1]<-90+ε)bbox[1]=-90;if(bbox[2]>180-ε)bbox[2]=180;if(bbox[3]>90-ε)bbox[3]=90}if(verbose){console.warn("bounds: "+bbox.join(" ")+" ("+system.name+")")}if(Q0){transform=prequantize(objects,bbox,Q0,Q1);if(verbose){console.warn("pre-quantization: "+transform.scale.map(function(k){return system.formatDistance(k)}).join(" "))}}if(system===systems.spherical&&stitchPoles){stitch(objects,transform)}var topology=topologize(objects);if(Q0)topology.transform=transform;topology.bbox=bbox;if(verbose){console.warn("topology: "+topology.arcs.length+" arcs, "+topology.arcs.reduce(function(p,v){return p+v.length},0)+" points")}if(Q1&&Q1!==Q0){postquantize(topology,Q0,Q1);transform=topology.transform;if(verbose){console.warn("post-quantization: "+transform.scale.map(function(k){return system.formatDistance(k)}).join(" "))}}if(Q1){delta(topology)}return topology}},{"./bounds":12,"./compute-id":15,"./coordinate-systems":16,"./delta":17,"./geomify":19,"./post-quantize":20,"./pre-quantize":21,"./stitch":27,"./topology/index":34,"./transform-properties":38,"./type":39}],29:[function(_dereq_,module,exports){var join=_dereq_("./join");module.exports=function(topology){var junctions=join(topology),coordinates=topology.coordinates,lines=topology.lines,rings=topology.rings;for(var i=0,n=lines.length;i>1),t;start=size)throw new Error("full hashmap");matchKey=keystore[index=index+1&mask]}keystore[index]=key;valstore[index]=value;--free;return value}function maybeSet(key,value){var index=hash(key)&mask,matchKey=keystore[index],collisions=0;while(matchKey!=keyEmpty){if(equal(matchKey,key))return valstore[index];if(++collisions>=size)throw new Error("full hashmap");matchKey=keystore[index=index+1&mask]}keystore[index]=key;valstore[index]=value;--free;return value}function get(key,missingValue){var index=hash(key)&mask,matchKey=keystore[index],collisions=0;while(matchKey!=keyEmpty){if(equal(matchKey,key))return valstore[index];if(++collisions>=size)break;matchKey=keystore[index=index+1&mask]}return missingValue}function keys(){var keys=[];for(var i=0,n=keystore.length;i=size)throw new Error("full hashset");match=store[index=index+1&mask]}store[index]=value;--free;return true}function has(value){var index=hash(value)&mask,match=store[index],collisions=0;while(match!=empty){if(equal(match,value))return true;if(++collisions>=size)break;match=store[index=index+1&mask]}return false}function values(){var values=[];for(var i=0,n=store.length;i=0){var rightIndex=rightByIndex[currentIndex];if((leftIndex!==previousIndex||rightIndex!==nextIndex)&&(leftIndex!==nextIndex||rightIndex!==previousIndex)){++junctionCount,junctionByIndex[currentIndex]=1}}else{leftByIndex[currentIndex]=previousIndex;rightByIndex[currentIndex]=nextIndex}}function index(){var indexByPoint=hashmap(coordinates.length*1.4,hashIndex,equalIndex,Int32Array,-1,Int32Array),indexes=new Int32Array(coordinates.length);for(var i=0,n=coordinates.length;i>7^uints[2]^uints[3];return hash&2147483647}},{}],38:[function(_dereq_,module,exports){module.exports=function(objects,propertyTransform){if(arguments.length<2)propertyTransform=function(){};function transformObject(object){if(object&&transformObjectType.hasOwnProperty(object.type))transformObjectType[object.type](object)}function transformFeature(feature){if(feature.properties){var properties0=feature.properties,properties1={},empty=true;for(var key0 in properties0){if(propertyTransform(properties1,key0,properties0[key0])){empty=false}}if(empty)delete feature.properties;else feature.properties=properties1}}var transformObjectType={Feature:transformFeature,FeatureCollection:function(collection){collection.features.forEach(transformFeature)}};for(var key in objects){transformObject(objects[key])}return objects}},{}],39:[function(_dereq_,module,exports){module.exports=function(types){for(var type in typeDefaults){if(!(type in types)){types[type]=typeDefaults[type]}}types.defaults=typeDefaults;return types};var typeDefaults={Feature:function(feature){if(feature.geometry)this.geometry(feature.geometry)},FeatureCollection:function(collection){var features=collection.features,i=-1,n=features.length;while(++i1){var geomsByArc=[],geom;function arc(i){var j=i<0?~i:i;(geomsByArc[j]||(geomsByArc[j]=[])).push({i:i,g:geom})}function line(arcs){arcs.forEach(arc)}function polygon(arcs){arcs.forEach(line)}function geometry(o){if(o.type==="GeometryCollection")o.geometries.forEach(geometry);else if(o.type in geometryType)geom=o,geometryType[o.type](o.arcs)}var geometryType={LineString:line,MultiLineString:polygon,Polygon:polygon,MultiPolygon:function(arcs){arcs.forEach(polygon)}};geometry(o);geomsByArc.forEach(arguments.length<3?function(geoms){arcs.push(geoms[0].i)}:function(geoms){if(filter(geoms[0].g,geoms[geoms.length-1].g))arcs.push(geoms[0].i)})}else{for(var i=0,n=topology.arcs.length;i0}polygons.forEach(function(polygon){if(!polygon._){var component=[],neighbors=[polygon];polygon._=1;components.push(component);while(polygon=neighbors.pop()){component.push(polygon);polygon.forEach(function(ring){ring.forEach(function(arc){polygonsByArc[arc<0?~arc:arc].forEach(function(polygon){if(!polygon._){polygon._=1;neighbors.push(polygon)}})})})}}});polygons.forEach(function(polygon){delete polygon._});return{type:"MultiPolygon",arcs:components.map(function(polygons){var arcs=[];polygons.forEach(function(polygon){polygon.forEach(function(ring){ring.forEach(function(arc){if(polygonsByArc[arc<0?~arc:arc].length<2){arcs.push(arc)}})})});arcs=stitchArcs(topology,arcs);if((n=arcs.length)>1){var sgn=exterior(polygons[0][0]);for(var i=0,t;i>>1;if(a[mid]0)object=array[size],down(array[object._=0]=object,0);return removed};heap.remove=function(removed){var i=removed._,object;if(array[i]!==removed)return;if(i!==--size)object=array[size],(compareArea(object,removed)<0?up:down)(array[object._=i]=object,i);return i};function up(object,i){while(i>0){var j=(i+1>>1)-1,parent=array[j];if(compareArea(object,parent)>=0)break;array[parent._=i]=parent;array[object._=i=j]=object}}function down(object,i){while(true){var r=i+1<<1,l=r-1,j=i,child=array[j];if(l0)){var t=r.shift();t()}},!0),function(n){r.push(n),window.postMessage("process-tick","*")}}return function(n){setTimeout(n,0)}}(),t.title="browser",t.browser=!0,t.env={},t.argv=[],t.on=r,t.addListener=r,t.once=r,t.off=r,t.removeListener=r,t.removeAllListeners=r,t.emit=r,t.binding=function(){throw new Error("process.binding is not supported")},t.cwd=function(){return"/"},t.chdir=function(){throw new Error("process.chdir is not supported")}},{}],5:[function(n,e){function r(n,e,r){function t(n){return n>=200&&300>n||304===n}function o(){void 0===a.status||t(a.status)?e.call(a,null,a):e.call(a,a,null)}var i=!1;if("undefined"==typeof window.XMLHttpRequest)return e(Error("Browser not supported"));if("undefined"==typeof r){var u=n.match(/^\s*https?:\/\/[^\/]*/);r=u&&u[0]!==location.protocol+"//"+location.domain+(location.port?":"+location.port:"")}var a=new window.XMLHttpRequest;if(r&&!("withCredentials"in a)){a=new window.XDomainRequest;var s=e;e=function(){if(i)s.apply(this,arguments);else{var n=this,e=arguments;setTimeout(function(){s.apply(n,e)},0)}}}return"onload"in a?a.onload=o:a.onreadystatechange=function(){4===a.readyState&&o()},a.onerror=function(n){e.call(this,n||!0,null),e=function(){}},a.onprogress=function(){},a.ontimeout=function(n){e.call(this,n,null),e=function(){}},a.onabort=function(n){e.call(this,n,null),e=function(){}},a.open("GET",n,!0),a.send(null),i=!0,a}"undefined"!=typeof e&&(e.exports=r)},{}],6:[function(n,e){function r(n){return!!n.match(/(Lat)(itude)?/gi)}function t(n){return!!n.match(/(L)(on|ng)(gitude)?/i)}function o(n){return"object"==typeof n?Object.keys(n).length:0}function i(n){var e=[",",";"," ","|"],r=[];return e.forEach(function(e){var t=c(e).parse(n);if(t.length>=1){for(var i=o(t[0]),u=0;u= N) return EOF; // special case: end of file\n if (eol) return eol = false, EOL; // special case: end of line\n\n // special case: quotes\n var j = I;\n if (text.charCodeAt(j) === 34) {\n var i = j;\n while (i++ < N) {\n if (text.charCodeAt(i) === 34) {\n if (text.charCodeAt(i + 1) !== 34) break;\n ++i;\n }\n }\n I = i + 2;\n var c = text.charCodeAt(i + 1);\n if (c === 13) {\n eol = true;\n if (text.charCodeAt(i + 2) === 10) ++I;\n } else if (c === 10) {\n eol = true;\n }\n return text.substring(j + 1, i).replace(/""/g, "\\"");\n }\n\n // common case: find next delimiter or newline\n while (I < N) {\n var c = text.charCodeAt(I++), k = 1;\n if (c === 10) eol = true; // \\n\n else if (c === 13) { eol = true; if (text.charCodeAt(I) === 10) ++I, ++k; } // \\r|\\r\\n\n else if (c !== delimiterCode) continue;\n return text.substring(j, I - k);\n }\n\n // special case: last token before EOF\n return text.substring(j);\n }\n\n while ((t = token()) !== EOF) {\n var a = [];\n while (t !== EOL && t !== EOF) {\n a.push(t);\n t = token();\n }\n if (f && !(a = f(a, n++))) continue;\n rows.push(a);\n }\n\n return rows;\n };\n\n dsv.format = function(rows) {\n if (Array.isArray(rows[0])) return dsv.formatRows(rows); // deprecated; use formatRows\n var fieldSet = {}, fields = [];\n\n // Compute unique fields in order of discovery.\n rows.forEach(function(row) {\n for (var field in row) {\n if (!(field in fieldSet)) {\n fields.push(fieldSet[field] = field);\n }\n }\n });\n\n return [fields.map(formatValue).join(delimiter)].concat(rows.map(function(row) {\n return fields.map(function(field) {\n return formatValue(row[field]);\n }).join(delimiter);\n })).join("\\n");\n };\n\n dsv.formatRows = function(rows) {\n return rows.map(formatRow).join("\\n");\n };\n\n function formatRow(row) {\n return row.map(formatValue).join(delimiter);\n }\n\n function formatValue(text) {\n return reFormat.test(text) ? "\\"" + text.replace(/\\"/g, "\\"\\"") + "\\"" : text;\n }\n\n return dsv;\n}\n;return dsv')()},{fs:2}],8:[function(n,e){e.exports=function(n,e){if(e||(e="NSEW"),"string"!=typeof n)return null;var r=/^([0-9.]+)°? *(?:([0-9.]+)['’′‘] *)?(?:([0-9.]+)(?:''|"|”|″) *)?([NSEW])?/,t=n.match(r);return t?t[4]&&-1===e.indexOf(t[4])?null:((t[1]?parseFloat(t[1]):0)+(t[2]?parseFloat(t[2])/60:0)+(t[3]?parseFloat(t[3])/3600:0))*(t[4]&&"S"===t[4]||"W"===t[4]?-1:1):null}},{}],9:[function(n,e,r){(function(t){toGeoJSON=function(){"use strict";function e(n){if(!n||!n.length)return 0;for(var e=0,r=0;ee?~e:e],o=t[0];return n.transform?(r=[0,0],t.forEach(function(n){r[0]+=n[0],r[1]+=n[1]})):r=t[t.length-1],0>e?[r,o]:[o,r]}function t(n,e){for(var r in n){var t=n[r];delete e[t.start],delete t.start,delete t.end,t.forEach(function(n){o[0>n?~n:n]=1}),a.push(t)}}var o={},i={},u={},a=[],s=-1;return e.forEach(function(r,t){var o,i=n.arcs[0>r?~r:r];i.length<3&&!i[1][0]&&!i[1][1]&&(o=e[++s],e[s]=r,e[t]=o)}),e.forEach(function(n){var e,t,o=r(n),a=o[0],s=o[1];if(e=u[a])if(delete u[e.end],e.push(n),e.end=s,t=i[s]){delete i[t.start];var f=t===e?e:e.concat(t);i[f.start=e.start]=u[f.end=t.end]=f}else i[e.start]=u[e.end]=e;else if(e=i[s])if(delete i[e.start],e.unshift(n),e.start=a,t=u[a]){delete u[t.end];var c=t===e?e:t.concat(e);i[c.start=t.start]=u[c.end=e.end]=c}else i[e.start]=u[e.end]=e;else e=[n],i[e.start=a]=u[e.end=s]=e}),t(u,i),t(i,u),e.forEach(function(n){o[0>n?~n:n]||a.push([n])}),a}function o(n,e,t){function o(n){var e=0>n?~n:n;(c[e]||(c[e]=[])).push({i:n,g:f})}function i(n){n.forEach(o)}function u(n){n.forEach(i)}function a(n){"GeometryCollection"===n.type?n.geometries.forEach(a):n.type in l&&(f=n,l[n.type](n.arcs))}var s=[];if(arguments.length>1){var f,c=[],l={LineString:i,MultiLineString:u,Polygon:u,MultiPolygon:function(n){n.forEach(u)}};a(e),c.forEach(arguments.length<3?function(n){s.push(n[0].i)}:function(n){t(n[0].g,n[n.length-1].g)&&s.push(n[0].i)})}else for(var p=0,d=n.arcs.length;d>p;++p)s.push(p);return{type:"MultiLineString",arcs:r(n,s)}}function i(e,t){function o(n){n.forEach(function(e){e.forEach(function(e){(u[e=0>e?~e:e]||(u[e]=[])).push(n)})}),a.push(n)}function i(n){return d(s(e,{type:"Polygon",arcs:[n]}).coordinates[0])>0}var u={},a=[],f=[];return t.forEach(function(n){"Polygon"===n.type?o(n.arcs):"MultiPolygon"===n.type&&n.arcs.forEach(o)}),a.forEach(function(n){if(!n._){var e=[],r=[n];for(n._=1,f.push(e);n=r.pop();)e.push(n),n.forEach(function(n){n.forEach(function(n){u[0>n?~n:n].forEach(function(n){n._||(n._=1,r.push(n))})})})}}),a.forEach(function(n){delete n._}),{type:"MultiPolygon",arcs:f.map(function(t){var o=[];if(t.forEach(function(n){n.forEach(function(n){n.forEach(function(n){u[0>n?~n:n].length<2&&o.push(n)})})}),o=r(e,o),(n=o.length)>1)for(var a,s=i(t[0][0]),f=0;n>f;++f)if(s===i(o[f])){a=o[0],o[0]=o[f],o[f]=a;break}return o})}}function u(n,e){return"GeometryCollection"===e.type?{type:"FeatureCollection",features:e.geometries.map(function(e){return a(n,e)})}:a(n,e)}function a(n,e){var r={type:"Feature",id:e.id,properties:e.properties||{},geometry:s(n,e)};return null==e.id&&delete r.id,r}function s(n,e){function r(n,e){e.length&&e.pop();for(var r,t=c[0>n?~n:n],o=0,i=t.length;i>o;++o)e.push(r=t[o].slice()),s(r,o);0>n&&f(e,i)}function t(n){return n=n.slice(),s(n,0),n}function o(n){for(var e=[],t=0,o=n.length;o>t;++t)r(n[t],e);return e.length<2&&e.push(e[0].slice()),e}function i(n){for(var e=o(n);e.length<4;)e.push(e[0].slice());return e}function u(n){return n.map(i)}function a(n){var e=n.type;return"GeometryCollection"===e?{type:e,geometries:n.geometries.map(a)}:e in l?{type:e,coordinates:l[e](n)}:null}var s=m(n.transform),c=n.arcs,l={Point:function(n){return t(n.coordinates)},MultiPoint:function(n){return n.coordinates.map(t)},LineString:function(n){return o(n.arcs)},MultiLineString:function(n){return n.arcs.map(o)},Polygon:function(n){return u(n.arcs)},MultiPolygon:function(n){return n.arcs.map(u)}};return a(e)}function f(n,e){for(var r,t=n.length,o=t-e;o<--t;)r=n[o],n[o++]=n[t],n[t]=r}function c(n,e){for(var r=0,t=n.length;t>r;){var o=r+t>>>1;n[o]n&&(n=~n);var r=o[n];r?r.push(e):o[n]=[e]})}function r(n,r){n.forEach(function(n){e(n,r)})}function t(n,e){"GeometryCollection"===n.type?n.geometries.forEach(function(n){t(n,e)}):n.type in u&&u[n.type](n.arcs,e)}var o={},i=n.map(function(){return[]}),u={LineString:e,MultiLineString:r,Polygon:r,MultiPolygon:function(n,e){n.forEach(function(n){r(n,e)})}};n.forEach(t);for(var a in o)for(var s=o[a],f=s.length,l=0;f>l;++l)for(var p=l+1;f>p;++p){var d,g=s[l],v=s[p];(d=i[g])[a=c(d,v)]!==v&&d.splice(a,0,v),(d=i[v])[a=c(d,g)]!==g&&d.splice(a,0,g)}return i}function p(n,e){function r(n){u.remove(n),n[1][2]=e(n),u.push(n)}var t,o=m(n.transform),i=y(n.transform),u=h(),a=0;for(e||(e=g),n.arcs.forEach(function(n){var r=[];n.forEach(o);for(var i=1,a=n.length-1;a>i;++i)t=n.slice(i-1,i+2),t[1][2]=e(t),r.push(t),u.push(t);n[0][2]=n[a][2]=1/0;for(var i=0,a=r.length;a>i;++i)t=r[i],t.previous=r[i-1],t.next=r[i+1]});t=u.pop();){var s=t.previous,f=t.next;t[1][2]0;){var r=(e+1>>1)-1,o=t[r];if(v(n,o)>=0)break;t[o._=e]=o,t[n._=e=r]=n}}function e(n,e){for(;;){var r=e+1<<1,i=r-1,u=e,a=t[u];if(o>i&&v(t[i],a)<0&&(a=t[u=i]),o>r&&v(t[r],a)<0&&(a=t[u=r]),u===e)break;t[a._=e]=a,t[n._=e=u]=n}}var r={},t=[],o=0;return r.push=function(e){return n(t[e._=o]=e,o++),o},r.pop=function(){if(!(0>=o)){var n,r=t[0];return--o>0&&(n=t[o],e(t[n._=0]=n,0)),r}},r.remove=function(r){var i,u=r._;if(t[u]===r)return u!==--o&&(i=t[o],(v(i,r)<0?n:e)(t[i._=u]=i,u)),u},r}function m(n){if(!n)return w;var e,r,t=n.scale[0],o=n.scale[1],i=n.translate[0],u=n.translate[1];return function(n,a){a||(e=r=0),n[0]=(e+=n[0])*t+i,n[1]=(r+=n[1])*o+u}}function y(n){if(!n)return w;var e,r,t=n.scale[0],o=n.scale[1],i=n.translate[0],u=n.translate[1];return function(n,a){a||(e=r=0);var s=(n[0]-i)/t|0,f=(n[1]-u)/o|0;n[0]=s-e,n[1]=f-r,e=s,r=f}}function w(){}var x={version:"1.6.8",mesh:function(n){return s(n,o.apply(this,arguments))},meshArcs:o,merge:function(n){return s(n,i.apply(this,arguments))},mergeArcs:i,feature:u,neighbors:l,presimplify:p};"function"==typeof e&&e.amd?e(x):"object"==typeof t&&t.exports?t.exports=x:this.topojson=x}()},{}],11:[function(n,e){function r(n){function e(e){var r=n.substring(h).match(e);return r?(h+=r[0].length,r[0]):null}function r(n){return n&&v.match(/\d+/)&&(n.crs={type:"name",properties:{name:"urn:ogc:def:crs:EPSG::"+v}}),n}function t(){e(/^\s*/)}function o(){t();for(var n,r=0,o=[],i=[o],u=o;n=e(/^(\()/)||e(/^(\))/)||e(/^(\,)/)||e(/^[-+]?([0-9]*\.[0-9]+|[0-9]+)/);){if("("==n)i.push(u),u=[],i[i.length-1].push(u),r++;else if(")"==n){if(u=i.pop(),!u)return;if(r--,0===r)break}else if(","===n)u=[],i[i.length-1].push(u);else{if(isNaN(parseFloat(n)))return null;u.push(parseFloat(n))}t()}return 0!==r?null:o}function i(){for(var n,r,o=[];r=e(/^[-+]?([0-9]*\.[0-9]+|[0-9]+)/)||e(/^(\,)/);)","==r?(o.push(n),n=[]):(n||(n=[]),n.push(parseFloat(r))),t();return n&&o.push(n),o.length?o:null}function u(){if(!e(/^(point)/i))return null;if(t(),!e(/^(\()/))return null;var n=i();return n?(t(),e(/^(\))/)?{type:"Point",coordinates:n[0]}:null):null}function a(){if(!e(/^(multipoint)/i))return null;t();var n=o();return n?(t(),{type:"MultiPoint",coordinates:n}):null}function s(){if(!e(/^(multilinestring)/i))return null;t();var n=o();return n?(t(),{type:"MultiLineString",coordinates:n}):null}function f(){if(!e(/^(linestring)/i))return null;if(t(),!e(/^(\()/))return null;var n=i();return n?e(/^(\))/)?{type:"LineString",coordinates:n}:null:null}function c(){return e(/^(polygon)/i)?(t(),{type:"Polygon",coordinates:o()}):null}function l(){return e(/^(multipolygon)/i)?(t(),{type:"MultiPolygon",coordinates:o()}):null}function p(){var n,r=[];if(!e(/^(geometrycollection)/i))return null;if(t(),!e(/^(\()/))return null;for(;n=d();)r.push(n),t(),e(/^(\,)/),t();return e(/^(\))/)?{type:"GeometryCollection",geometries:r}:null}function d(){return u()||f()||c()||a()||s()||l()||p()}var g=n.split(";"),n=g.pop(),v=(g.shift()||"").split("=").pop(),h=0;return r(d())}function t(n){function e(n){return 2===n.length?n[0]+" "+n[1]:3===n.length?n[0]+" "+n[1]+" "+n[2]:void 0}function r(n){return n.map(e).join(", ")}function o(n){return n.map(r).map(u).join(", ")}function i(n){return n.map(o).map(u).join(", ")}function u(n){return"("+n+")"}switch("Feature"===n.type&&(n=n.geometry),n.type){case"Point":return"POINT ("+e(n.coordinates)+")";case"LineString":return"LINESTRING ("+r(n.coordinates)+")";case"Polygon":return"POLYGON ("+o(n.coordinates)+")";case"MultiPoint":return"MULTIPOINT ("+r(n.coordinates)+")";case"MultiPolygon":return"MULTIPOLYGON ("+i(n.coordinates)+")";case"MultiLineString":return"MULTILINESTRING ("+o(n.coordinates)+")";case"GeometryCollection":return"GEOMETRYCOLLECTION ("+n.geometries.map(t).join(", ")+")";default:throw new Error("stringify requires a valid GeoJSON Feature or geometry object as input")}}e.exports=r,e.exports.parse=r,e.exports.stringify=t},{}]},{},[1])(1)}); \ No newline at end of file From 799741f6b209a017cf6a870999e616e4338c253b Mon Sep 17 00:00:00 2001 From: MauricioVidulin Date: Fri, 11 Jul 2014 15:48:11 +0200 Subject: [PATCH 09/13] enhance multipolygon draw functionalities #88 --- django_project/imagery_requests/forms.py | 41 ++++- .../templates/request_form.html | 142 ++++++++++++++---- 2 files changed, 154 insertions(+), 29 deletions(-) diff --git a/django_project/imagery_requests/forms.py b/django_project/imagery_requests/forms.py index 096e384..bfc4179 100644 --- a/django_project/imagery_requests/forms.py +++ b/django_project/imagery_requests/forms.py @@ -11,6 +11,9 @@ class ImageryRequestForm(forms.ModelForm): widget=forms.Textarea({'hidden': ''}), error_messages={'required': 'Please select an area of interest.'} ) + multipolygon_errors = forms.CharField( + widget=forms.Textarea({'hidden': ''}) + ) class Meta: model = ImageryRequest @@ -34,9 +37,30 @@ def __init__(self, *args, **kwargs): {'required': 'Please select a question set.'} ) + def clean(self): + cleaned_data = self.cleaned_data + cleaned_area_of_interest = cleaned_data.get('area_of_interest') + + if cleaned_area_of_interest: + multipolygon_errors = cleaned_data.get('multipolygon_errors') + + if multipolygon_errors: + msg = 'Fix errors. ' + multipolygon_errors + self._errors['area_of_interest'] = self.error_class([msg]) + + del cleaned_data['area_of_interest'] + + return cleaned_data + class ImageryRequestEditForm(forms.ModelForm): - area_of_interest = forms.CharField(widget=forms.Textarea({'hidden': ''})) + area_of_interest = forms.CharField( + widget=forms.Textarea({'hidden': ''}), + error_messages={'required': 'Please select an area of interest.'} + ) + multipolygon_errors = forms.CharField( + widget=forms.Textarea({'hidden': ''}) + ) class Meta: model = ImageryRequest @@ -66,6 +90,21 @@ def __init__(self, *args, **kwargs): self.fields['question_set'].choices ) + def clean(self): + cleaned_data = self.cleaned_data + cleaned_area_of_interest = cleaned_data.get('area_of_interest') + + if cleaned_area_of_interest: + multipolygon_errors = cleaned_data.get('multipolygon_errors') + + if multipolygon_errors: + msg = 'Fix errors. ' + multipolygon_errors + self._errors['area_of_interest'] = self.error_class([msg]) + + del cleaned_data['area_of_interest'] + + return cleaned_data + class RequestDateForm(forms.ModelForm): date = forms.DateField( diff --git a/django_project/imagery_requests/templates/request_form.html b/django_project/imagery_requests/templates/request_form.html index 62dfe86..ac69294 100644 --- a/django_project/imagery_requests/templates/request_form.html +++ b/django_project/imagery_requests/templates/request_form.html @@ -74,6 +74,7 @@ {% endif %} {{ form.area_of_interest }} + {{ form.multipolygon_errors }} {% for radio in form.question_set %}
@@ -102,30 +103,45 @@ {% endblock %} \ No newline at end of file From 95af4d9580fc77ecb4f2a79135ad67215f8ff239 Mon Sep 17 00:00:00 2001 From: MauricioVidulin Date: Sat, 12 Jul 2014 12:58:21 +0200 Subject: [PATCH 10/13] make multipolygon_errors field not required --- django_project/imagery_requests/forms.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/django_project/imagery_requests/forms.py b/django_project/imagery_requests/forms.py index bfc4179..6693c38 100644 --- a/django_project/imagery_requests/forms.py +++ b/django_project/imagery_requests/forms.py @@ -12,7 +12,8 @@ class ImageryRequestForm(forms.ModelForm): error_messages={'required': 'Please select an area of interest.'} ) multipolygon_errors = forms.CharField( - widget=forms.Textarea({'hidden': ''}) + widget=forms.Textarea({'hidden': ''}), + required=False ) class Meta: @@ -59,7 +60,8 @@ class ImageryRequestEditForm(forms.ModelForm): error_messages={'required': 'Please select an area of interest.'} ) multipolygon_errors = forms.CharField( - widget=forms.Textarea({'hidden': ''}) + widget=forms.Textarea({'hidden': ''}), + required=False ) class Meta: From 1b19de3049a87d46d90682f08ad5e245cb61cab9 Mon Sep 17 00:00:00 2001 From: MauricioVidulin Date: Sat, 12 Jul 2014 13:32:12 +0200 Subject: [PATCH 11/13] add support for adding custom error tooltips to map --- .../templates/request_form.html | 113 +++++++++++++----- 1 file changed, 81 insertions(+), 32 deletions(-) diff --git a/django_project/imagery_requests/templates/request_form.html b/django_project/imagery_requests/templates/request_form.html index ac69294..3730ebd 100644 --- a/django_project/imagery_requests/templates/request_form.html +++ b/django_project/imagery_requests/templates/request_form.html @@ -164,12 +164,49 @@ featureGroup: drawnItems } }); + map.addControl(drawControl); + // Initial vars for custom error tooltip. + errorShown = false; + custom_tooltip = null; + hideErrorTimeout = null; + + function show_custom_tooltip(error_string) { + // Hide error tooltip if exists. + hideErrorTooltip(); + + custom_tooltip = new L.Tooltip(map); + custom_tooltip.updateContent({text: error_string}); + custom_tooltip.showAsError(); + errorShown = true; + // Hide the error after 2 seconds + hideErrorTimeout = setTimeout(L.Util.bind(hideErrorTooltip), 4000); + } + + function hideErrorTooltip() { + errorShown = false; + if (custom_tooltip) { + clearHideErrorTimeout(); + custom_tooltip.dispose(); + custom_tooltip = null; + } + } + + function clearHideErrorTimeout() { + if (hideErrorTimeout) { + clearTimeout(hideErrorTimeout); + hideErrorTimeout = null; + } + } + // Signal if drawing is activated. Initially it's not active. var drawIsActive = false; // Signal if edition is saved. Used for draw:editstop event. var isEditionSaved = false; + // Initial vars for multipolygon checks during drawing. + var previous_point = null; + var current_point = null; map.on('draw:drawstart', function (e) { drawIsActive = true; @@ -181,9 +218,8 @@ drawnItems.addLayer(layer); - var trouble_polygons = [] - // Layers list. - var layers = drawnItems.getLayers(); + var trouble_polygons = []; + var layers = drawnItems.getLayers(); // Layers list. // Check after drawing: line segments intersection; point in another polygon; // polygon overlaps another polygon. @@ -198,6 +234,7 @@ var trouble_polygons = result.trouble_polygons; var error_messages = result.error_messages; + // If there are trouble polygons make them red. if (trouble_polygons.length > 0) { var polygon_counter = 0; @@ -213,7 +250,6 @@ } } - // Dump geojson to textarea. var geojson_string = create_geojson_multipolygon_string(); $('#id_area_of_interest').val(geojson_string); @@ -230,6 +266,9 @@ }); $('#id_multipolygon_errors').val(error_string); + + // Show error tooltip. + show_custom_tooltip(error_string); } // Set previuos_point var to null because next polygon won't have @@ -254,8 +293,7 @@ // If edition is saved do the checking process, // if not (i.e. canceled ) just pass. if (isEditionSaved == true) { - // Layers list. - var layers = drawnItems.getLayers(); + var layers = drawnItems.getLayers(); // Layers list. var polygons = get_cleaned_polygons(layers); var result = check_after_drawing(polygons); @@ -299,6 +337,9 @@ }); $('#id_multipolygon_errors').val(error_string); + + // Show error tooltip. + show_custom_tooltip(error_string); } } @@ -306,8 +347,7 @@ }); map.on('draw:deleted', function (e) { - // Layers list. - var layers = drawnItems.getLayers(); + var layers = drawnItems.getLayers(); // Layers list. var polygons = get_cleaned_polygons(layers); var result = check_after_drawing(polygons); @@ -335,7 +375,6 @@ polygon_counter++; } - // if (trouble_polygons.length == 0) { if (layers.length == 0) { // Empty area_of_interest text area. $('#id_area_of_interest').val(''); @@ -358,13 +397,13 @@ }); $('#id_multipolygon_errors').val(error_string); + + // Show error tooltip. + show_custom_tooltip(error_string); } } }); - var previous_point = null; - var current_point = null; - map.on('click', function(e) { // If drawing is activated check if new point is inside existing polygon or // new line segment intersects with any of the existing line segments. @@ -398,6 +437,8 @@ $('.leaflet-draw-actions li a:first')[0].click(); // Revert value of current_point to previous_point. current_point = previous_point; + // Show error tooltip. + show_custom_tooltip("Area shouldn't intersect with another area.") return false; // break jquery each loop and current loop } } @@ -409,6 +450,8 @@ if (pointIsInPolygon == true) { // Programatically click link to delete created vertex. $('.leaflet-draw-actions li a:eq(1)')[0].click(); + // Show error tooltip. + show_custom_tooltip("Can't place point inside polygon.") return false; // break jquery each loop } } @@ -419,6 +462,12 @@ } }); + map.on('mousemove', function(e) { + if (errorShown) { + custom_tooltip.updatePosition(e.latlng); + } + }); + // When link for drawing polygon is clicked then add event listener for 'cancel' link. $('.leaflet-draw-section .leaflet-draw-toolbar a:eq(0)').on('click', function() { // When cancel drawing link is clicked (either programatically or manually) @@ -431,7 +480,7 @@ function check_after_drawing(polygons) { var trouble_polygons = []; - var error_messages = [] + var error_messages = []; // Check if any polygon intersects any other polygon. // Check if any polygon covers any other polygon. @@ -478,7 +527,7 @@ // Check if any line segment of any polygon intersects with any line // segment of any other polygon. function polygon_intersects_polygon(error_messages, trouble_polygons, poly_a, index_a, poly_b, index_b) { - var error_message = "Area shouldn't intersect with another area." + var error_message = "Area shouldn't intersect with another area."; for (var k = 0, h = poly_a.length - 1; k < poly_a.length; h = k++) { var p_1a = {x: poly_a[k][0], y: poly_a[k][1]}; @@ -515,7 +564,7 @@ // Check if any polygon covers any other polygon. We're actually checking // if point from one polygon lies inside another polygon. function polygon_covers_polygon(error_messages, trouble_polygons, poly_a, index_a, poly_b, index_b) { - var error_message = "Area shouldn't cover another area." + var error_message = "Area shouldn't cover another area."; jQuery.each(poly_a, function(point_index, point) { var pointIsInPolygon = point_in_polygon(point, poly_b); @@ -542,7 +591,7 @@ } function polygon_intersects_itself(error_messages, trouble_polygons, poly_a, index_a) { - var error_message = "Area shouldn't intersect with itself." + var error_message = "Area shouldn't intersect with itself."; for (var i = 0, j = poly_a.length - 1; i < poly_a.length; j = i++) { var p_1 = {x: poly_a[i][0], y: poly_a[i][1]} @@ -574,6 +623,22 @@ } } + // Function that checks if point is inside polygon. + function point_in_polygon (point, polygon) { + var x = point[0], y = point[1]; + + var inside = false; + for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { + var xi = polygon[i][0], yi = polygon[i][1]; + var xj = polygon[j][0], yj = polygon[j][1]; + + var intersect = ((yi > y) != (yj > y)) + && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + if (intersect) inside = !inside; + } + return inside; + } + function get_cleaned_polygons(layers) { var polygons = []; @@ -592,22 +657,6 @@ return polygons; } - // Function that checks if point is inside polygon. - function point_in_polygon (point, polygon) { - var x = point[0], y = point[1]; - - var inside = false; - for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { - var xi = polygon[i][0], yi = polygon[i][1]; - var xj = polygon[j][0], yj = polygon[j][1]; - - var intersect = ((yi > y) != (yj > y)) - && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - if (intersect) inside = !inside; - } - return inside; - } - function create_geojson_multipolygon_string() { var geojson_string = '{"type": "MultiPolygon","coordinates": ['; From 79c2446f2d18c0898974432ff0159ee6a3a9e944 Mon Sep 17 00:00:00 2001 From: MauricioVidulin Date: Sat, 12 Jul 2014 13:35:37 +0200 Subject: [PATCH 12/13] move javascript from request_form.html to map.js --- .../imagery_requests/static/js/map.js | 570 ++++++++++++++++++ .../templates/request_form.html | 569 +---------------- 2 files changed, 571 insertions(+), 568 deletions(-) diff --git a/django_project/imagery_requests/static/js/map.js b/django_project/imagery_requests/static/js/map.js index a04b3f5..10ae062 100644 --- a/django_project/imagery_requests/static/js/map.js +++ b/django_project/imagery_requests/static/js/map.js @@ -165,4 +165,574 @@ function addWorldGeoJson() { }); }); }); +} + +function initRequestFormMap() { + // Initialise the FeatureGroup to store editable layers. + var drawnItems = new L.FeatureGroup(); + + drawnItems.on('layeradd', function() { + map.fitBounds(drawnItems.getBounds()); + }); + + // If area exists (i.e. we are editing previously saved request). + if ( $('#id_area_of_interest').val() != '') { + var polygon = $('#id_area_of_interest').val(); + // if polygon is in geojson + if (polygon[0] == '{') { + var geojsonFeature = jQuery.parseJSON(polygon); + var parsed_polygon = L.geoJson(geojsonFeature) + + // Add multipolygon parts separately to use leaflet draw features. + jQuery.each(parsed_polygon._layers, function(index_1, obj_1) { + jQuery.each(obj_1._layers, function(index_2, obj_2) { + drawnItems.addLayer(obj_2); + }); + }); + } else { + // polygon is in wkt. + var parsed_polygon = omnivore.wkt.parse(polygon); + // Add multipolygon parts separately to use leaflet draw features. + jQuery.each(parsed_polygon._layers, function(index_1, obj_1) { + jQuery.each(obj_1._layers, function(index_2, obj_2) { + drawnItems.addLayer(obj_2); + }); + }); + } + } + + map.addLayer(drawnItems); + + // Initialise the draw control and pass it the FeatureGroup of editable layers + var drawControl = new L.Control.Draw({ + draw: { + polygon: { + allowIntersection: false, + drawError: { + color: '#e1e100', + message: 'Draw normal polygons!' + }, + shapeOptions: { + color: '#FFA500' + } + }, + polyline: false, + rectangle: false, + circle: false, + marker: false, + }, + edit: { + featureGroup: drawnItems + } + }); + + map.addControl(drawControl); + + // Initial vars for custom error tooltip. + errorShown = false; + custom_tooltip = null; + hideErrorTimeout = null; + + function show_custom_tooltip(error_string) { + // Hide error tooltip if exists. + hideErrorTooltip(); + + custom_tooltip = new L.Tooltip(map); + custom_tooltip.updateContent({text: error_string}); + custom_tooltip.showAsError(); + errorShown = true; + // Hide the error after 2 seconds + hideErrorTimeout = setTimeout(L.Util.bind(hideErrorTooltip), 4000); + } + + function hideErrorTooltip() { + errorShown = false; + if (custom_tooltip) { + clearHideErrorTimeout(); + custom_tooltip.dispose(); + custom_tooltip = null; + } + } + + function clearHideErrorTimeout() { + if (hideErrorTimeout) { + clearTimeout(hideErrorTimeout); + hideErrorTimeout = null; + } + } + + // Signal if drawing is activated. Initially it's not active. + var drawIsActive = false; + // Signal if edition is saved. Used for draw:editstop event. + var isEditionSaved = false; + // Initial vars for multipolygon checks during drawing. + var previous_point = null; + var current_point = null; + + map.on('draw:drawstart', function (e) { + drawIsActive = true; + }); + + map.on('draw:created', function (e) { + var layer = e.layer; + drawIsActive = false; + + drawnItems.addLayer(layer); + + var trouble_polygons = []; + var layers = drawnItems.getLayers(); // Layers list. + + // Check after drawing: line segments intersection; point in another polygon; + // polygon overlaps another polygon. + // Important because sometimes checks during drawing are not made. + // It can happen because sometimes map.on('click') event listener doesn't + // "get" that click (e.g., when you draw points on map quickly). + if (layers.length > 1) { + + var polygons = get_cleaned_polygons(layers); + + var result = check_after_drawing(polygons); + var trouble_polygons = result.trouble_polygons; + var error_messages = result.error_messages; + + // If there are trouble polygons make them red. + if (trouble_polygons.length > 0) { + var polygon_counter = 0; + + for (var key in drawnItems._layers) { + if($.inArray(polygon_counter, trouble_polygons) != -1) { + drawnItems._layers[key].setStyle({ + fillColor: '#ca4c4d', + color: '#ca4c4d' + }); + } + polygon_counter++; + } + } + } + + // Dump geojson to textarea. + var geojson_string = create_geojson_multipolygon_string(); + $('#id_area_of_interest').val(geojson_string); + + if (trouble_polygons.length == 0) { + // Empty multipolygon errors field. + $('#id_multipolygon_errors').val(''); + } else { + // Put multipolygon errors text in textarea. + error_string = ''; + + jQuery.each(error_messages, function() { + error_string += this + ' '; + }); + + $('#id_multipolygon_errors').val(error_string); + + // Show error tooltip. + show_custom_tooltip(error_string); + } + + // Set previuos_point var to null because next polygon won't have + // previous_point immediately. + previous_point = null; + }); + + map.on('draw:drawstop', function (e) { + drawIsActive = false; + }); + + map.on('draw:edited', function (e) { + // draw:edited gets called after saving edition, BUT before draw:editstop + //(i.e. draw:editstop is called after both save and cancel edit). + // So our custom logic that we want to implement after saving edition + // should go in draw:editstop. + + isEditionSaved = true; + }); + + map.on('draw:editstop', function (e) { + // If edition is saved do the checking process, + // if not (i.e. canceled ) just pass. + if (isEditionSaved == true) { + var layers = drawnItems.getLayers(); // Layers list. + var polygons = get_cleaned_polygons(layers); + + var result = check_after_drawing(polygons); + var trouble_polygons = result.trouble_polygons; + var error_messages = result.error_messages; + + // Set "error" color to trouble polygons and default color to + // regular polygons. + var polygon_counter = 0; + + for (var key in drawnItems._layers) { + if($.inArray(polygon_counter, trouble_polygons) != -1) { + drawnItems._layers[key].setStyle({ + fillColor: '#ca4c4d', + color: '#ca4c4d' + }); + } else { + // Set default color. + drawnItems._layers[key].setStyle({ + fillColor: '#FFA500', + color: '#FFA500' + }); + } + + polygon_counter++; + } + + // Dump geojson to textarea. + var geojson_string = create_geojson_multipolygon_string(); + $('#id_area_of_interest').val(geojson_string); + + if (trouble_polygons.length == 0) { + // Empty multipolygon errors field. + $('#id_multipolygon_errors').val(''); + } else { + // Put multipolygon errors text in textarea. + error_string = ''; + + jQuery.each(error_messages, function() { + error_string += this + ' '; + }); + + $('#id_multipolygon_errors').val(error_string); + + // Show error tooltip. + show_custom_tooltip(error_string); + } + } + + isEditionSaved = false; + }); + + map.on('draw:deleted', function (e) { + var layers = drawnItems.getLayers(); // Layers list. + var polygons = get_cleaned_polygons(layers); + + var result = check_after_drawing(polygons); + var trouble_polygons = result.trouble_polygons; + var error_messages = result.error_messages; + + // Set "error" color to trouble polygons and default color to + // regular polygons. + var polygon_counter = 0; + + for (var key in drawnItems._layers) { + if($.inArray(polygon_counter, trouble_polygons) != -1) { + drawnItems._layers[key].setStyle({ + fillColor: '#ca4c4d', + color: '#ca4c4d' + }); + } else { + // Set default color. + drawnItems._layers[key].setStyle({ + fillColor: '#FFA500', + color: '#FFA500' + }); + } + + polygon_counter++; + } + + if (layers.length == 0) { + // Empty area_of_interest text area. + $('#id_area_of_interest').val(''); + // Empty multipolygon errors field. + $('#id_multipolygon_errors').val(''); + } else { + // Dump geojson to textarea. + var geojson_string = create_geojson_multipolygon_string(); + $('#id_area_of_interest').val(geojson_string); + + if (trouble_polygons.length == 0) { + // Empty multipolygon errors field. + $('#id_multipolygon_errors').val(''); + } else { + // Put multipolygon errors text in textarea. + error_string = ''; + + jQuery.each(error_messages, function() { + error_string += this + ' '; + }); + + $('#id_multipolygon_errors').val(error_string); + + // Show error tooltip. + show_custom_tooltip(error_string); + } + } + }); + + map.on('click', function(e) { + // If drawing is activated check if new point is inside existing polygon or + // new line segment intersects with any of the existing line segments. + if (drawIsActive == true) { + current_point = [e.layerPoint.x, e.layerPoint.y]; + var layers = drawnItems.getLayers(); + + // If other polygons exist then go through the checking process. + if (layers.length > 0) { + polygons = get_cleaned_polygons(layers) + + // Check if current point is inside existing polygon, + // (important only for the first point of polygon). + // For each polygon check if current line segment intersects + // any line segement of the polygon. + jQuery.each(polygons, function() { + // If previous_point exists (means that line segment exists) + // check intersections. + if (previous_point) { + var p1 = {x: previous_point[0], y: previous_point[1]}; + var p2 = {x: current_point[0], y: current_point[1]}; + + for (i=0, j=this.length-1;i y) != (yj > y)) + && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); + if (intersect) inside = !inside; + } + return inside; + } + + function get_cleaned_polygons(layers) { + var polygons = []; + + // For each layer (polygon) get it's points coords. in pixels. + jQuery.each(layers, function () { + var polygon_points = this._originalPoints; + var clean_polygon_points = []; + + jQuery.each(polygon_points, function() { + clean_polygon_points.push([this.x, this.y]); + }); + + polygons.push(clean_polygon_points); + }); + + return polygons; + } + + function create_geojson_multipolygon_string() { + var geojson_string = '{"type": "MultiPolygon","coordinates": ['; + + for (var key in drawnItems._layers) { + var layer = drawnItems._layers[key]; + geojson_string += JSON.stringify(layer.toGeoJSON()['geometry']['coordinates']) + ','; + } + + geojson_string = geojson_string.slice(0, -1); // remove last comma. + geojson_string += ']}'; // finish geojson string. + + return geojson_string + } + + function create_geojson_polygon_string() { + var geojson_string = '{"type": "Polygon","coordinates": ['; + } } \ No newline at end of file diff --git a/django_project/imagery_requests/templates/request_form.html b/django_project/imagery_requests/templates/request_form.html index 3730ebd..7d62886 100644 --- a/django_project/imagery_requests/templates/request_form.html +++ b/django_project/imagery_requests/templates/request_form.html @@ -106,574 +106,7 @@ .checkbox(); initMap(); - - // Initialise the FeatureGroup to store editable layers. - var drawnItems = new L.FeatureGroup(); - - drawnItems.on('layeradd', function() { - map.fitBounds(drawnItems.getBounds()); - }); - - // If area exists (i.e. we are editing previously saved request). - if ( $('#id_area_of_interest').val() != '') { - var polygon = $('#id_area_of_interest').val(); - // if polygon is in geojson - if (polygon[0] == '{') { - var geojsonFeature = jQuery.parseJSON(polygon); - var parsed_polygon = L.geoJson(geojsonFeature) - - // Add multipolygon parts separately to use leaflet draw features. - jQuery.each(parsed_polygon._layers, function(index_1, obj_1) { - jQuery.each(obj_1._layers, function(index_2, obj_2) { - drawnItems.addLayer(obj_2); - }); - }); - } else { - // polygon is in wkt. - var parsed_polygon = omnivore.wkt.parse(polygon); - // Add multipolygon parts separately to use leaflet draw features. - jQuery.each(parsed_polygon._layers, function(index_1, obj_1) { - jQuery.each(obj_1._layers, function(index_2, obj_2) { - drawnItems.addLayer(obj_2); - }); - }); - } - } - - map.addLayer(drawnItems); - - // Initialise the draw control and pass it the FeatureGroup of editable layers - var drawControl = new L.Control.Draw({ - draw: { - polygon: { - allowIntersection: false, - drawError: { - color: '#e1e100', - message: 'Draw normal polygons!' - }, - shapeOptions: { - color: '#FFA500' - } - }, - polyline: false, - rectangle: false, - circle: false, - marker: false, - }, - edit: { - featureGroup: drawnItems - } - }); - - map.addControl(drawControl); - - // Initial vars for custom error tooltip. - errorShown = false; - custom_tooltip = null; - hideErrorTimeout = null; - - function show_custom_tooltip(error_string) { - // Hide error tooltip if exists. - hideErrorTooltip(); - - custom_tooltip = new L.Tooltip(map); - custom_tooltip.updateContent({text: error_string}); - custom_tooltip.showAsError(); - errorShown = true; - // Hide the error after 2 seconds - hideErrorTimeout = setTimeout(L.Util.bind(hideErrorTooltip), 4000); - } - - function hideErrorTooltip() { - errorShown = false; - if (custom_tooltip) { - clearHideErrorTimeout(); - custom_tooltip.dispose(); - custom_tooltip = null; - } - } - - function clearHideErrorTimeout() { - if (hideErrorTimeout) { - clearTimeout(hideErrorTimeout); - hideErrorTimeout = null; - } - } - - // Signal if drawing is activated. Initially it's not active. - var drawIsActive = false; - // Signal if edition is saved. Used for draw:editstop event. - var isEditionSaved = false; - // Initial vars for multipolygon checks during drawing. - var previous_point = null; - var current_point = null; - - map.on('draw:drawstart', function (e) { - drawIsActive = true; - }); - - map.on('draw:created', function (e) { - var layer = e.layer; - drawIsActive = false; - - drawnItems.addLayer(layer); - - var trouble_polygons = []; - var layers = drawnItems.getLayers(); // Layers list. - - // Check after drawing: line segments intersection; point in another polygon; - // polygon overlaps another polygon. - // Important because sometimes checks during drawing are not made. - // It can happen because sometimes map.on('click') event listener doesn't - // "get" that click (e.g., when you draw points on map quickly). - if (layers.length > 1) { - - var polygons = get_cleaned_polygons(layers); - - var result = check_after_drawing(polygons); - var trouble_polygons = result.trouble_polygons; - var error_messages = result.error_messages; - - // If there are trouble polygons make them red. - if (trouble_polygons.length > 0) { - var polygon_counter = 0; - - for (var key in drawnItems._layers) { - if($.inArray(polygon_counter, trouble_polygons) != -1) { - drawnItems._layers[key].setStyle({ - fillColor: '#ca4c4d', - color: '#ca4c4d' - }); - } - polygon_counter++; - } - } - } - - // Dump geojson to textarea. - var geojson_string = create_geojson_multipolygon_string(); - $('#id_area_of_interest').val(geojson_string); - - if (trouble_polygons.length == 0) { - // Empty multipolygon errors field. - $('#id_multipolygon_errors').val(''); - } else { - // Put multipolygon errors text in textarea. - error_string = ''; - - jQuery.each(error_messages, function() { - error_string += this + ' '; - }); - - $('#id_multipolygon_errors').val(error_string); - - // Show error tooltip. - show_custom_tooltip(error_string); - } - - // Set previuos_point var to null because next polygon won't have - // previous_point immediately. - previous_point = null; - }); - - map.on('draw:drawstop', function (e) { - drawIsActive = false; - }); - - map.on('draw:edited', function (e) { - // draw:edited gets called after saving edition, BUT before draw:editstop - //(i.e. draw:editstop is called after both save and cancel edit). - // So our custom logic that we want to implement after saving edition - // should go in draw:editstop. - - isEditionSaved = true; - }); - - map.on('draw:editstop', function (e) { - // If edition is saved do the checking process, - // if not (i.e. canceled ) just pass. - if (isEditionSaved == true) { - var layers = drawnItems.getLayers(); // Layers list. - var polygons = get_cleaned_polygons(layers); - - var result = check_after_drawing(polygons); - var trouble_polygons = result.trouble_polygons; - var error_messages = result.error_messages; - - // Set "error" color to trouble polygons and default color to - // regular polygons. - var polygon_counter = 0; - - for (var key in drawnItems._layers) { - if($.inArray(polygon_counter, trouble_polygons) != -1) { - drawnItems._layers[key].setStyle({ - fillColor: '#ca4c4d', - color: '#ca4c4d' - }); - } else { - // Set default color. - drawnItems._layers[key].setStyle({ - fillColor: '#FFA500', - color: '#FFA500' - }); - } - - polygon_counter++; - } - - // Dump geojson to textarea. - var geojson_string = create_geojson_multipolygon_string(); - $('#id_area_of_interest').val(geojson_string); - - if (trouble_polygons.length == 0) { - // Empty multipolygon errors field. - $('#id_multipolygon_errors').val(''); - } else { - // Put multipolygon errors text in textarea. - error_string = ''; - - jQuery.each(error_messages, function() { - error_string += this + ' '; - }); - - $('#id_multipolygon_errors').val(error_string); - - // Show error tooltip. - show_custom_tooltip(error_string); - } - } - - isEditionSaved = false; - }); - - map.on('draw:deleted', function (e) { - var layers = drawnItems.getLayers(); // Layers list. - var polygons = get_cleaned_polygons(layers); - - var result = check_after_drawing(polygons); - var trouble_polygons = result.trouble_polygons; - var error_messages = result.error_messages; - - // Set "error" color to trouble polygons and default color to - // regular polygons. - var polygon_counter = 0; - - for (var key in drawnItems._layers) { - if($.inArray(polygon_counter, trouble_polygons) != -1) { - drawnItems._layers[key].setStyle({ - fillColor: '#ca4c4d', - color: '#ca4c4d' - }); - } else { - // Set default color. - drawnItems._layers[key].setStyle({ - fillColor: '#FFA500', - color: '#FFA500' - }); - } - - polygon_counter++; - } - - if (layers.length == 0) { - // Empty area_of_interest text area. - $('#id_area_of_interest').val(''); - // Empty multipolygon errors field. - $('#id_multipolygon_errors').val(''); - } else { - // Dump geojson to textarea. - var geojson_string = create_geojson_multipolygon_string(); - $('#id_area_of_interest').val(geojson_string); - - if (trouble_polygons.length == 0) { - // Empty multipolygon errors field. - $('#id_multipolygon_errors').val(''); - } else { - // Put multipolygon errors text in textarea. - error_string = ''; - - jQuery.each(error_messages, function() { - error_string += this + ' '; - }); - - $('#id_multipolygon_errors').val(error_string); - - // Show error tooltip. - show_custom_tooltip(error_string); - } - } - }); - - map.on('click', function(e) { - // If drawing is activated check if new point is inside existing polygon or - // new line segment intersects with any of the existing line segments. - if (drawIsActive == true) { - current_point = [e.layerPoint.x, e.layerPoint.y]; - var layers = drawnItems.getLayers(); - - // If other polygons exist then go through the checking process. - if (layers.length > 0) { - polygons = get_cleaned_polygons(layers) - - // Check if current point is inside existing polygon, - // (important only for the first point of polygon). - // For each polygon check if current line segment intersects - // any line segement of the polygon. - jQuery.each(polygons, function() { - // If previous_point exists (means that line segment exists) - // check intersections. - if (previous_point) { - var p1 = {x: previous_point[0], y: previous_point[1]}; - var p2 = {x: current_point[0], y: current_point[1]}; - - for (i=0, j=this.length-1;i y) != (yj > y)) - && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); - if (intersect) inside = !inside; - } - return inside; - } - - function get_cleaned_polygons(layers) { - var polygons = []; - - // For each layer (polygon) get it's points coords. in pixels. - jQuery.each(layers, function () { - var polygon_points = this._originalPoints; - var clean_polygon_points = []; - - jQuery.each(polygon_points, function() { - clean_polygon_points.push([this.x, this.y]); - }); - - polygons.push(clean_polygon_points); - }); - - return polygons; - } - - function create_geojson_multipolygon_string() { - var geojson_string = '{"type": "MultiPolygon","coordinates": ['; - - for (var key in drawnItems._layers) { - var layer = drawnItems._layers[key]; - geojson_string += JSON.stringify(layer.toGeoJSON()['geometry']['coordinates']) + ','; - } - - geojson_string = geojson_string.slice(0, -1); // remove last comma. - geojson_string += ']}'; // finish geojson string. - - return geojson_string - } - - function create_geojson_polygon_string() { - var geojson_string = '{"type": "Polygon","coordinates": ['; - } + initRequestFormMap(); {% endblock %} \ No newline at end of file From 008cb2a94c88841b57e6f015ec9dab836d72fa17 Mon Sep 17 00:00:00 2001 From: MauricioVidulin Date: Fri, 1 Aug 2014 11:11:20 +0200 Subject: [PATCH 13/13] fix stuff --- .../imagery_requests/static/js/map.js | 48 ++++++++----------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/django_project/imagery_requests/static/js/map.js b/django_project/imagery_requests/static/js/map.js index 10ae062..7a49a8f 100644 --- a/django_project/imagery_requests/static/js/map.js +++ b/django_project/imagery_requests/static/js/map.js @@ -176,8 +176,8 @@ function initRequestFormMap() { }); // If area exists (i.e. we are editing previously saved request). - if ( $('#id_area_of_interest').val() != '') { - var polygon = $('#id_area_of_interest').val(); + if ( jQuery('#id_area_of_interest').val() != '') { + var polygon = jQuery('#id_area_of_interest').val(); // if polygon is in geojson if (polygon[0] == '{') { var geojsonFeature = jQuery.parseJSON(polygon); @@ -242,7 +242,7 @@ function initRequestFormMap() { custom_tooltip.showAsError(); errorShown = true; // Hide the error after 2 seconds - hideErrorTimeout = setTimeout(L.Util.bind(hideErrorTooltip), 4000); + hideErrorTimeout = setTimeout(L.Util.bind(hideErrorTooltip), 2000); } function hideErrorTooltip() { @@ -313,11 +313,11 @@ function initRequestFormMap() { // Dump geojson to textarea. var geojson_string = create_geojson_multipolygon_string(); - $('#id_area_of_interest').val(geojson_string); + jQuery('#id_area_of_interest').val(geojson_string); if (trouble_polygons.length == 0) { // Empty multipolygon errors field. - $('#id_multipolygon_errors').val(''); + jQuery('#id_multipolygon_errors').val(''); } else { // Put multipolygon errors text in textarea. error_string = ''; @@ -326,7 +326,7 @@ function initRequestFormMap() { error_string += this + ' '; }); - $('#id_multipolygon_errors').val(error_string); + jQuery('#id_multipolygon_errors').val(error_string); // Show error tooltip. show_custom_tooltip(error_string); @@ -339,6 +339,9 @@ function initRequestFormMap() { map.on('draw:drawstop', function (e) { drawIsActive = false; + + current_point = null; + previous_point = null; }); map.on('draw:edited', function (e) { @@ -384,11 +387,11 @@ function initRequestFormMap() { // Dump geojson to textarea. var geojson_string = create_geojson_multipolygon_string(); - $('#id_area_of_interest').val(geojson_string); + jQuery('#id_area_of_interest').val(geojson_string); if (trouble_polygons.length == 0) { // Empty multipolygon errors field. - $('#id_multipolygon_errors').val(''); + jQuery('#id_multipolygon_errors').val(''); } else { // Put multipolygon errors text in textarea. error_string = ''; @@ -397,7 +400,7 @@ function initRequestFormMap() { error_string += this + ' '; }); - $('#id_multipolygon_errors').val(error_string); + jQuery('#id_multipolygon_errors').val(error_string); // Show error tooltip. show_custom_tooltip(error_string); @@ -438,17 +441,17 @@ function initRequestFormMap() { if (layers.length == 0) { // Empty area_of_interest text area. - $('#id_area_of_interest').val(''); + jQuery('#id_area_of_interest').val(''); // Empty multipolygon errors field. - $('#id_multipolygon_errors').val(''); + jQuery('#id_multipolygon_errors').val(''); } else { // Dump geojson to textarea. var geojson_string = create_geojson_multipolygon_string(); - $('#id_area_of_interest').val(geojson_string); + jQuery('#id_area_of_interest').val(geojson_string); if (trouble_polygons.length == 0) { // Empty multipolygon errors field. - $('#id_multipolygon_errors').val(''); + jQuery('#id_multipolygon_errors').val(''); } else { // Put multipolygon errors text in textarea. error_string = ''; @@ -457,7 +460,7 @@ function initRequestFormMap() { error_string += this + ' '; }); - $('#id_multipolygon_errors').val(error_string); + jQuery('#id_multipolygon_errors').val(error_string); // Show error tooltip. show_custom_tooltip(error_string); @@ -494,8 +497,8 @@ function initRequestFormMap() { var intersects = L.LineUtil.segmentsIntersect(p1, p2, p3, p4); if (intersects == true) { - // Programatically click link to delete the last vertex. - $('.leaflet-draw-actions li a:first')[0].click(); + // Delete the last vertex. + drawControl._toolbars['draw']._modes.polygon.handler.deleteLastVertex(); // Revert value of current_point to previous_point. current_point = previous_point; // Show error tooltip. @@ -509,8 +512,8 @@ function initRequestFormMap() { // If current point is inside existing polygon then delete // that point and show error message. if (pointIsInPolygon == true) { - // Programatically click link to delete created vertex. - $('.leaflet-draw-actions li a:eq(1)')[0].click(); + // Cancel drawing polygon. + drawControl._toolbars['draw']._modes.polygon.handler.disable(); // Show error tooltip. show_custom_tooltip("Can't place point inside polygon.") return false; // break jquery each loop @@ -529,15 +532,6 @@ function initRequestFormMap() { } }); - // When link for drawing polygon is clicked then add event listener for 'cancel' link. - $('.leaflet-draw-section .leaflet-draw-toolbar a:eq(0)').on('click', function() { - // When cancel drawing link is clicked (either programatically or manually) - // reset value of current point and previous point. - $('.leaflet-draw-actions li a:eq(1)').on('click', function() { - current_point = null; - previous_point = null; - }); - }); function check_after_drawing(polygons) { var trouble_polygons = [];