Hi Anthony and Dave, Thank you for your suggestions and I have implemented this visualiser:). I add button in the cell and when it's clicked the geometry is shown in a map. Here is the screenshots of it: https://photos.app.goo.gl/fQVBRcmZJ3YcPLcL8
Attached is the patch for this geometry viewer. Please review it. Thank you. Anthony DeBarros <adebar...@gmail.com> 于2018年5月26日周六 上午12:47写道: > I agree the map is the ultimate tool. However, I do think that being able >> to quickly view a single geometry from within the grid makes a lot of sense >> - and for users who want to easily visualise a single geometry, will be a >> great deal more convenient than the map. >> >> Note that I'm not talking about anything complex here - just a >> convenience panel that renders the geometry, and maybe has zoom and >> scrolling. The viewing tab would be where the real magic happens - handling >> the map overlay and the ability to display many geometries at once. >> >> >> As a user, I would agree with Dave here. A good example is a table that > holds the polygons of each county in the U.S. or each country in Europe. It > would be very helpful to view each individually as well as collectively if > I were to select all in a query. > > >> >> -- >> Dave Page >> Blog: http://pgsnake.blogspot.com >> Twitter: @pgsnake >> >> EnterpriseDB UK: http://www.enterprisedb.com >> The Enterprise PostgreSQL Company >> >
diff --git a/web/package.json b/web/package.json index a551af1..102aad2 100644 --- a/web/package.json +++ b/web/package.json @@ -63,6 +63,7 @@ "css-loader": "0.14.0", "cssnano": "^3.10.0", "dropzone": "^5.1.1", + "element-resize-detector": "^1.1.14", "eonasdan-bootstrap-datetimepicker": "^4.17.47", "exports-loader": "~0.6.4", "flotr2": "^0.1.0", @@ -74,6 +75,7 @@ "jquery": "3.3.1", "jquery-contextmenu": "^2.6.4", "jquery-ui": "^1.12.1", + "leaflet": "^1.3.3", "moment": "^2.20.1", "mousetrap": "^1.6.1", "prop-types": "^15.5.10", @@ -91,7 +93,8 @@ "underscore": "^1.8.3", "underscore.string": "^3.3.4", "watchify": "~3.9.0", - "webcabin-docker": "git+https://github.com/EnterpriseDB/wcDocker" + "webcabin-docker": "git+https://github.com/EnterpriseDB/wcDocker", + "wkx": "^0.4.5" }, "scripts": { "linter": "yarn eslint --no-eslintrc -c .eslintrc.js --ext .js --ext .jsx .", diff --git a/web/pgadmin/static/css/style.css b/web/pgadmin/static/css/style.css index ed88eab..d9d39bf 100644 --- a/web/pgadmin/static/css/style.css +++ b/web/pgadmin/static/css/style.css @@ -14,6 +14,7 @@ @import '~webcabin-docker/Build/wcDocker.css'; @import '~acitree/css/aciTree.css'; @import '~spectrum-colorpicker/spectrum.css'; +@import '~leaflet/dist/leaflet.css'; @import '~codemirror/lib/codemirror.css'; @import '~codemirror/addon/dialog/dialog.css'; diff --git a/web/pgadmin/static/js/slickgrid/formatters.js b/web/pgadmin/static/js/slickgrid/formatters.js index adfcf79..ea003d4 100644 --- a/web/pgadmin/static/js/slickgrid/formatters.js +++ b/web/pgadmin/static/js/slickgrid/formatters.js @@ -4,6 +4,8 @@ * @module Formatters * @namespace Slick */ +import {Geometry} from 'wkx'; +import {Buffer} from 'buffer'; (function($) { // register namespace @@ -15,6 +17,7 @@ 'Checkmark': CheckmarkFormatter, 'Text': TextFormatter, 'Binary': BinaryFormatter, + 'EWKB': EWKBFormatter, }, }, }); @@ -111,4 +114,39 @@ return '<span class=\'pull-left disabled_cell\'>[' + _.escape(value) + ']</span>'; } } + + function EWKBFormatter(row, cell, value, columnDef, dataContext) { + // If column has default value, set placeholder + var data = NullAndDefaultFormatter(row, cell, value, columnDef, dataContext); + if (data) { + return data; + } else { + let geometry; + try { + let buffer = new Buffer(value, 'hex'); + geometry = Geometry.parse(buffer); + } catch (e) { + //unsupported geometry type + return '<button title="Can not render geometry of this type" class="btn-xs btn-default btn-ewkb-viewer disabled">' + + '<i class="fa fa-eye-slash" aria-hidden="true"></i>' + + '</button>' + + _.escape(value); + } + + if (geometry.hasZ) { + //the viewer can not render 3d geometry + return '<button title="Can not render 3d geometry" class="btn-xs btn-default btn-ewkb-viewer disabled">' + + '<i class="fa fa-eye-slash" aria-hidden="true"></i>' + + '</button>' + + _.escape(value); + } + else { + return '<button title="View Geometry" class="btn-xs btn-default btn-ewkb-viewer btn-view-ewkb-enabled">' + + '<i class="fa fa-eye" aria-hidden="true"></i>' + + '</button>' + + _.escape(value); + } + + } + } })(window.jQuery); diff --git a/web/pgadmin/static/js/sqleditor/geometry_viewer.js b/web/pgadmin/static/js/sqleditor/geometry_viewer.js new file mode 100644 index 0000000..4254547 --- /dev/null +++ b/web/pgadmin/static/js/sqleditor/geometry_viewer.js @@ -0,0 +1,140 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2018, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import gettext from 'sources/gettext'; +import Alertify from 'pgadmin.alertifyjs'; +import {Geometry} from 'wkx'; +import {Buffer} from 'buffer'; +import $ from 'jquery'; +import elementResizeDetectorMaker from 'element-resize-detector'; +import L from 'leaflet'; + +// fix the icon url issue according to https://github.com/Leaflet/Leaflet/issues/4849 +import 'leaflet/dist/images/marker-icon.png'; +import 'leaflet/dist/images/marker-icon-2x.png'; +import 'leaflet/dist/images/marker-shadow.png'; +import 'leaflet/dist/images/layers.png'; +import 'leaflet/dist/images/layers-2x.png'; + + +let GeometryViewerDialog = { + + 'dialog': function (value) { + + Alertify.mapDialog || Alertify.dialog('mapDialog', function () { + let $container = $('<div class="ewkb-viewer-container"></div>'); + let geomLayer = L.geoJSON(); + let osmLayer = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '<a target="_blank" href="https://www.openstreetmap.org/copyright">© OpenStreetMap</a>', + }); + let lmap; + + return { + main: function (geometry) { + //reset map + geomLayer.clearLayers(); + lmap.eachLayer(function (layer) { + lmap.removeLayer(layer); + }); + + try { + geomLayer.addData(geometry.toGeoJSON()); + } catch (e) { + // Invalid LatLng object: (NaN, NaN) + lmap.setView([0, 0], 0); + return; + } + + if (geometry.toWkt().endsWith('EMPTY')) { + // empty geometry + lmap.setView([0, 0], 0); + } else { + let bounds = geomLayer.getBounds(); + bounds = bounds.pad(0.1); + let maxLength = Math.max(bounds.getNorth() - bounds.getSouth(), + bounds.getEast() - bounds.getWest()); + if (geometry.srid === 4326) { + lmap.options.crs = L.CRS.EPSG3857; + lmap.setMinZoom(0); + osmLayer.addTo(lmap); + } else { + lmap.options.crs = L.CRS.Simple; + if (maxLength >= 180) { + // calculate the min zoom level to enable the map to fit the whole geometry. + let minZoom = Math.floor(Math.log2(360 / maxLength)) - 2; + lmap.setMinZoom(minZoom); + } else { + lmap.setMinZoom(0); + } + } + geomLayer.addTo(lmap); + + if (maxLength > 0) { + lmap.fitBounds(bounds); + } else { + lmap.setView(bounds.getCenter(), 5); + } + } + }, + + setup: function () { + return { + options: { + closable: true, + frameless: true, + padding: false, + overflow: false, + title: gettext('Geometry Viewer'), + }, + }; + }, + + build: function () { + let div = $container.get(0); + this.elements.content.appendChild(div); + lmap = L.map(div); + geomLayer.addTo(lmap); + // add resize event listener to resize map + let erd = elementResizeDetectorMaker(); + erd.listenTo(div, function () { + setTimeout(function () { + lmap.invalidateSize(); + }, 200); + }); + Alertify.pgDialogBuild.apply(this); + this.elements.dialog.style.width = '80%'; + this.elements.dialog.style.height = '60%'; + }, + + prepare: function () { + this.elements.dialog.style.width = '80%'; + this.elements.dialog.style.height = '60%'; + }, + }; + }); + + let geometry; + try { + let buffer = new Buffer(value, 'hex'); + geometry = Geometry.parse(buffer); + } catch (e) { + Alertify.alert(gettext('Geometry Viewer Error'), gettext('Can not render geometry of this type')); + } + + if (geometry.hasZ) { + Alertify.alert(gettext('Geometry Viewer Error'), gettext('Can not render 3d geometry')); + } else { + Alertify.mapDialog(geometry); + } + }, + +}; + + +module.exports = GeometryViewerDialog; diff --git a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css index 75ead26..d48f5ad 100644 --- a/web/pgadmin/tools/sqleditor/static/css/sqleditor.css +++ b/web/pgadmin/tools/sqleditor/static/css/sqleditor.css @@ -641,3 +641,20 @@ input.editor-checkbox:focus { .connection-status-hide { display: none; } + +/* For slickgrid EWKB data viewer button */ +.btn-ewkb-viewer{ + float: right; + position: relative; +} + +/* For EWKB data viewer dialog */ +.ewkb-viewer-container{ + width: 100%; + height: 100%; +} + +/* For leaflet map background */ +.leaflet-container { + background: #fff; +} diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js index 1316c7b..87c6c8d 100644 --- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js +++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js @@ -15,6 +15,7 @@ define('tools.querytool', [ 'sources/sqleditor/execute_query', 'sources/sqleditor/query_tool_http_error_handler', 'sources/sqleditor/filter_dialog', + 'sources/sqleditor/geometry_viewer', 'sources/history/index.js', 'sourcesjsx/history/query_history', 'react', 'react-dom', @@ -36,7 +37,7 @@ define('tools.querytool', [ ], function( babelPollyfill, gettext, url_for, $, _, S, alertify, pgAdmin, Backbone, codemirror, pgExplain, GridSelector, ActiveCellCapture, clipboard, copyData, RangeSelectionHelper, handleQueryOutputKeyboardEvent, - XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler, + XCellSelectionModel, setStagedRows, SqlEditorUtils, ExecuteQuery, httpErrorHandler, FilterHandler, GeometryViewerDialog, HistoryBundle, queryHistory, React, ReactDOM, keyboardShortcuts, queryToolActions, queryToolNotifications, Datagrid, modifyAnimation, calculateQueryRunTime, callRenderAfterPoll, queryToolPref) { @@ -727,7 +728,13 @@ define('tools.querytool', [ } else if (c.cell == 'binary') { // We do not support editing binary data in SQL editor and data grid. options['formatter'] = Slick.Formatters.Binary; - } else { + } else if (c.cell == 'geometry' || c.cell == 'geography' ){ + options['editor'] = is_editable ? Slick.Editors.pgText : + Slick.Editors.ReadOnlypgText; + // EWKB formatter for viewing geometry data. + options['formatter'] = Slick.Formatters.EWKB; + } + else { options['editor'] = is_editable ? Slick.Editors.pgText : Slick.Editors.ReadOnlypgText; options['formatter'] = Slick.Formatters.Text; @@ -826,6 +833,15 @@ define('tools.querytool', [ setStagedRows.bind(editor_data)); } + // listen for 'view geometry' button click event in datagrid + grid.onClick.subscribe(function (e, args) { + if ($(e.target).hasClass('btn-view-ewkb-enabled') || $(e.target).parent().hasClass('btn-view-ewkb-enabled')) { + var value = dataView.getItem(args.row)[grid.getColumns()[args.cell].field]; + //show geometry viewer dialog + GeometryViewerDialog.dialog(value); + } + }); + grid.onColumnsResized.subscribe(function() { var columns = this.getColumns(); _.each(columns, function(col) { @@ -2382,6 +2398,12 @@ define('tools.querytool', [ case 'bytea[]': col_cell = 'binary'; break; + case 'geometry': + col_cell = 'geometry'; + break; + case 'geography': + col_cell = 'geography'; + break; default: col_cell = 'string'; } diff --git a/web/regression/javascript/geometry_viewer/slickgrid_ewkb_formatter_spec.js b/web/regression/javascript/geometry_viewer/slickgrid_ewkb_formatter_spec.js new file mode 100644 index 0000000..1f3b8e3 --- /dev/null +++ b/web/regression/javascript/geometry_viewer/slickgrid_ewkb_formatter_spec.js @@ -0,0 +1,172 @@ +///////////////////////////////////////////////////////////// +// +// pgAdmin 4 - PostgreSQL Tools +// +// Copyright (C) 2013 - 2018, The pgAdmin Development Team +// This software is released under the PostgreSQL Licence +// +////////////////////////////////////////////////////////////// + +import 'sources/slickgrid/formatters'; + +describe('EWKB formatter test', function () { + let EWKBFromatter = window.Slick.Formatters.EWKB; + const row = undefined; + const cell = undefined; + + describe('format supported geometry', function () { + it('should return the view button for geometry', function () { + // POINT(0 0) + let ewkb = '010100000000000000000000000000000000000000'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('View Geometry'); + }); + it('should return the view button for geometry', function () { + // LINESTRING(0 0,1 1,1 2) + let ewkb = '01020000000300000000000000000000000000000000000000000000000000' + + 'F03F000000000000F03F000000000000F03F0000000000000040'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('View Geometry'); + }); + it('should return the view button for geometry', function () { + // GEOMETRYCOLLECTION(POINT(2 3),LINESTRING(2 3,3 4)) + let ewkb = '01070000000200000001010000000000000000000040000000000000084001' + + '02000000020000000000000000000040000000000000084000000000000008400000000' + + '000001040'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('View Geometry'); + }); + it('should return the view button for geometry', function () { + // SRID=32632;POINT(0 0) + let ewkb = '0101000020787F000000000000000000000000000000000000'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('View Geometry'); + }); + it('should return the view button for geometry', function () { + // SRID=4326;MULTIPOINTM(0 0 0,1 2 1) + let ewkb = '0104000060E610000002000000010100004000000000000000000000000000' + + '00000000000000000000000101000040000000000000F03F00000000000000400000000' + + '00000F03F'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('View Geometry'); + }); + it('should return the view button for geometry', function () { + // GEOMETRYCOLLECTION(POINT(2 3),LINESTRING(2 3,3 4)) + let ewkb = '01070000000200000001010000000000000000000040000000000000084001' + + '02000000020000000000000000000040000000000000084000000000000008400000000' + + '000001040'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('View Geometry'); + }); + it('should return the view button for geometry', function () { + // POINT EMPTY + let ewkb = '0101000000000000000000F87F000000000000F87F'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('View Geometry'); + }); + it('should return the view button for geometry', function () { + // LINESTRING EMPTY + let ewkb = '010200000000000000'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('View Geometry'); + }); + it('should return the view button for geometry', function () { + // POLYGON EMPTY + let ewkb = '010300000000000000'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('View Geometry'); + }); + it('should return the view button for geometry', function () { + // MULTIPOINT EMPTY + let ewkb = '010400000000000000'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('View Geometry'); + }); + it('should return the view button for geometry', function () { + // GEOMETRYCOLLECTION EMPTY + let ewkb = '010700000000000000'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('View Geometry'); + }); + }); + + describe('format 3d geometry', function () { + it('should return the disabled button for 3d geometry', function () { + // POINT(0 0 0) + let ewkb = '0101000080000000000000F03F000000000000F03F000000000000F03F'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('Can not render 3d geometry'); + }); + + it('should return the disabled button for 3d geometry', function () { + // POINT(0 0 0 0) + let ewkb = '01010000C00000000000000000000000000000000000000000000000000000' + + '000000000000'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('Can not render 3d geometry'); + }); + + it('should return the disabled button for 3d geometry', function () { + let ewkb = '01060000800200000001030000800200000005000000000000000000000000' + + '00000000000000000000000000000000000000000010400000000000000000000000000' + + '00000000000000000001040000000000000104000000000000000000000000000000000' + + '00000000000010400000000000000000000000000000000000000000000000000000000' + + '00000000005000000000000000000F03F000000000000F03F0000000000000000000000' + + '0000000040000000000000F03F000000000000000000000000000000400000000000000' + + '0400000000000000000000000000000F03F000000000000004000000000000000000000' + + '00000000F03F000000000000F03F0000000000000000010300008001000000050000000' + + '00000000000F0BF000000000000F0BF0000000000000000000000000000F0BF00000000' + + '000000C0000000000000000000000000000000C000000000000000C0000000000000000' + + '000000000000000C0000000000000F0BF0000000000000000000000000000F0BF000000' + + '000000F0BF0000000000000000'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('Can not render 3d geometry'); + }); + }); + + describe('format unsupported geometry type', function () { + it('should return the disabled button for unsupported geometry', function () { + let ewkb = ''; + expect(EWKBFromatter(row, cell, ewkb)).toContain('Can not render geometry of this type'); + }); + it('should return the disabled button for unsupported geometry', function () { + // MULTICURVE( (0 0, 5 5), CIRCULARSTRING(4 0, 4 4, 8 4) ) + let ewkb = '010B0000000200000001020000000200000000000000000000000000000000' + + '00000000000000000014400000000000001440010800000003000000000000000000104' + + '00000000000000000000000000000104000000000000010400000000000002040000000' + + '0000001040'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('Can not render geometry of this type'); + }); + it('should return the disabled button for unsupported geometry', function () { + // POLYHEDRALSURFACE( ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)), ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)), ((1 1 0, 1 1 1, 1 0 1, 1 0 0, 1 1 0)), ((0 1 0, 0 1 1, 1 1 1, 1 1 0, 0 1 0)), ((0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)) ) + let ewkb = '010F0000800600000001030000800100000005000000000000000000000000' + + '00000000000000000000000000000000000000000000000000000000000000000000000' + + '000F03F0000000000000000000000000000F03F000000000000F03F0000000000000000' + + '000000000000F03F0000000000000000000000000000000000000000000000000000000' + + '00000000001030000800100000005000000000000000000000000000000000000000000' + + '0000000000000000000000000000000000000000F03F000000000000000000000000000' + + '0F03F000000000000F03F0000000000000000000000000000F03F000000000000000000' + + '00000000000000000000000000000000000000000000000000000000000000010300008' + + '00100000005000000000000000000000000000000000000000000000000000000000000' + + '000000F03F00000000000000000000000000000000000000000000F03F0000000000000' + + '000000000000000F03F00000000000000000000000000000000000000000000F03F0000' + + '00000000000000000000000000000000000000000000010300008001000000050000000' + + '00000000000F03F000000000000F03F0000000000000000000000000000F03F00000000' + + '0000F03F000000000000F03F000000000000F03F0000000000000000000000000000F03' + + 'F000000000000F03F00000000000000000000000000000000000000000000F03F000000' + + '000000F03F0000000000000000010300008001000000050000000000000000000000000' + + '000000000F03F00000000000000000000000000000000000000000000F03F0000000000' + + '00F03F000000000000F03F000000000000F03F000000000000F03F000000000000F03F0' + + '00000000000F03F00000000000000000000000000000000000000000000F03F00000000' + + '00000000010300008001000000050000000000000000000000000000000000000000000' + + '0000000F03F000000000000F03F0000000000000000000000000000F03F000000000000' + + 'F03F000000000000F03F000000000000F03F0000000000000000000000000000F03F000' + + '000000000F03F00000000000000000000000000000000000000000000F03F'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('Can not render geometry of this type'); + }); + it('should return the disabled button for unsupported geometry', function () { + // TRIANGLE ((0 0, 0 9, 9 0, 0 0)) + let ewkb = '01110000000100000004000000000000000000000000000000000000000000' + + '00000000000000000000000022400000000000002240000000000000000000000000000' + + '000000000000000000000'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('Can not render geometry of this type'); + }); + it('should return the disabled button for unsupported geometry', function () { + // TIN( ((0 0 0, 0 0 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 0 0 0)) ) + let ewkb = '01100000800200000001110000800100000004000000000000000000000000' + + '00000000000000000000000000000000000000000000000000000000000000000000000' + + '000F03F0000000000000000000000000000F03F00000000000000000000000000000000' + + '00000000000000000000000000000000011100008001000000040000000000000000000' + + '000000000000000000000000000000000000000000000000000000000000000F03F0000' + + '000000000000000000000000F03F000000000000F03F000000000000000000000000000' + + '0000000000000000000000000000000000000'; + expect(EWKBFromatter(row, cell, ewkb)).toContain('Can not render geometry of this type'); + }); + }); +}); diff --git a/web/yarn.lock b/web/yarn.lock index f0ff60b..9506f1e 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1234,6 +1234,10 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +batch-processor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/batch-processor/-/batch-processor-1.0.0.tgz#75c95c32b748e0850d10c2b168f6bdbe9891ace8" + bcrypt-pbkdf@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" @@ -3002,6 +3006,12 @@ electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.30: version "1.3.45" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.45.tgz#458ac1b1c5c760ce8811a16d2bfbd97ec30bafb8" +element-resize-detector@^1.1.14: + version "1.1.14" + resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.1.14.tgz#af064a0a618a820ad570a95c5eec5b77be0128c1" + dependencies: + batch-processor "^1.0.0" + elliptic@^6.0.0: version "6.4.0" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" @@ -5604,6 +5614,10 @@ lead@^1.0.0: dependencies: flush-write-stream "^1.0.2" +leaflet@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.3.3.tgz#5c8f2fd50e4a41ead93ab850dcd9e058811da9b9" + level-codec@~7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/level-codec/-/level-codec-7.0.1.tgz#341f22f907ce0f16763f24bddd681e395a0fb8a7" @@ -9611,6 +9625,12 @@ window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" +wkx@^0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/wkx/-/wkx-0.4.5.tgz#a85e15a6e69d1bfaec2f3c523be3dfa40ab861d0" + dependencies: + "@types/node" "*" + wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"