diff --git a/django_project/imagery_requests/forms.py b/django_project/imagery_requests/forms.py index 096e384..6693c38 100644 --- a/django_project/imagery_requests/forms.py +++ b/django_project/imagery_requests/forms.py @@ -11,6 +11,10 @@ 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': ''}), + required=False + ) class Meta: model = ImageryRequest @@ -34,9 +38,31 @@ 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': ''}), + required=False + ) class Meta: model = ImageryRequest @@ -66,6 +92,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/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( diff --git a/django_project/imagery_requests/static/js/map.js b/django_project/imagery_requests/static/js/map.js index a04b3f5..7a49a8f 100644 --- a/django_project/imagery_requests/static/js/map.js +++ b/django_project/imagery_requests/static/js/map.js @@ -165,4 +165,568 @@ 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 ( 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); + 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), 2000); + } + + 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(); + jQuery('#id_area_of_interest').val(geojson_string); + + if (trouble_polygons.length == 0) { + // Empty multipolygon errors field. + jQuery('#id_multipolygon_errors').val(''); + } else { + // Put multipolygon errors text in textarea. + error_string = ''; + + jQuery.each(error_messages, function() { + error_string += this + ' '; + }); + + jQuery('#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; + + current_point = null; + previous_point = null; + }); + + 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(); + jQuery('#id_area_of_interest').val(geojson_string); + + if (trouble_polygons.length == 0) { + // Empty multipolygon errors field. + jQuery('#id_multipolygon_errors').val(''); + } else { + // Put multipolygon errors text in textarea. + error_string = ''; + + jQuery.each(error_messages, function() { + error_string += this + ' '; + }); + + jQuery('#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. + jQuery('#id_area_of_interest').val(''); + // Empty multipolygon errors field. + jQuery('#id_multipolygon_errors').val(''); + } else { + // Dump geojson to textarea. + var geojson_string = create_geojson_multipolygon_string(); + jQuery('#id_area_of_interest').val(geojson_string); + + if (trouble_polygons.length == 0) { + // Empty multipolygon errors field. + jQuery('#id_multipolygon_errors').val(''); + } else { + // Put multipolygon errors text in textarea. + error_string = ''; + + jQuery.each(error_messages, function() { + error_string += this + ' '; + }); + + jQuery('#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 00b503e..7d62886 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,86 +103,10 @@ {% endblock %} \ No newline at end of file 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