From 96628abe747c1a9971c192bcd1809d516079f3fe Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Mon, 14 Apr 2025 12:28:05 -0600 Subject: [PATCH 01/46] Create GISDocuments as untitled when no path provided --- .../jupytergis_lab/notebook/gis_document.py | 15 ++++++--------- python/jupytergis_lab/src/notebookrenderer.ts | 12 +++++++----- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py index b5e8a74a4..f987e223a 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py @@ -46,8 +46,6 @@ class GISDocument(CommWidget): :param path: the path to the file that you would like to open. If not provided, a new empty document will be created. """ - path: Optional[Path] - def __init__( self, path: Optional[str | Path] = None, @@ -59,17 +57,16 @@ def __init__( pitch: Optional[float] = None, projection: Optional[str] = None, ): - if isinstance(path, str): - path = Path(path) - - self.path = path - - comm_metadata = GISDocument._path_to_comm(str(self.path) if self.path else None) + if isinstance(path, Path): + path = str(path) ydoc = Doc() super().__init__( - comm_metadata=dict(ymodel_name="@jupytergis:widget", **comm_metadata), + comm_metadata={ + "ymodel_name": "@jupytergis:widget", + **self._path_to_comm(path), + }, ydoc=ydoc, ) diff --git a/python/jupytergis_lab/src/notebookrenderer.ts b/python/jupytergis_lab/src/notebookrenderer.ts index cea775ad3..0295e6b58 100644 --- a/python/jupytergis_lab/src/notebookrenderer.ts +++ b/python/jupytergis_lab/src/notebookrenderer.ts @@ -171,11 +171,13 @@ export const notebookRendererPlugin: JupyterFrontEndPlugin = { }); } } else { - // If the user did not provide a path, do not create - localPath = PathExt.join( - PathExt.dirname(currentWidgetPath), - 'unsaved_project' - ); + // If the user did not provide a path, create an untitled document + let model = await app.serviceManager.contents.newUntitled({ + path: PathExt.dirname(currentWidgetPath), + type: 'file', + ext: '.jGIS' + }); + localPath = model.path; } const sharedModel = drive!.sharedModelFactory.createNew({ From b1ee829ad33ec791b82cdad31c3d7446fd1a94ae Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Mon, 14 Apr 2025 12:31:19 -0600 Subject: [PATCH 02/46] Remove `save_as()` method from GISDocument API, re-add `export_to_qgis()` We now create untitled documents when no path is passed in, so users can use the JupyterLab facilities to rename the document. --- .../jupytergis_lab/notebook/gis_document.py | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py index f987e223a..72b9ed2e3 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py @@ -106,23 +106,7 @@ def layer_tree(self) -> List[str | Dict]: """ return self._layerTree.to_py() - def save_as(self, path: str | Path) -> None: - """Save the document at a new path.""" - if isinstance(path, str): - path = Path(path) - - if path.name.lower().endswith(".qgz"): - _export_to_qgis(path) - self.path = path - return - - if not path.name.lower().endswith(".jgis"): - path = Path(str(path) + ".jGIS") - - path.write_text(json.dumps(self.to_py())) - self.path = path - - def _export_to_qgis(self, path: str | Path) -> bool: + def export_to_qgis(self, path: str | Path) -> bool: # Lazy import, jupytergis_qgis of qgis may not be installed from jupytergis_qgis.qgis_loader import export_project_to_qgis From c275e6e6a45e4a7d41ab1e3783f347afe04aa757 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Mon, 14 Apr 2025 12:40:52 -0600 Subject: [PATCH 03/46] Prefer `const` --- python/jupytergis_lab/src/notebookrenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/jupytergis_lab/src/notebookrenderer.ts b/python/jupytergis_lab/src/notebookrenderer.ts index 0295e6b58..caf0fa19a 100644 --- a/python/jupytergis_lab/src/notebookrenderer.ts +++ b/python/jupytergis_lab/src/notebookrenderer.ts @@ -172,7 +172,7 @@ export const notebookRendererPlugin: JupyterFrontEndPlugin = { } } else { // If the user did not provide a path, create an untitled document - let model = await app.serviceManager.contents.newUntitled({ + const model = await app.serviceManager.contents.newUntitled({ path: PathExt.dirname(currentWidgetPath), type: 'file', ext: '.jGIS' From 691a60b4f258a7a8b59cc345779857fc1f42c677 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Mon, 14 Apr 2025 13:04:07 -0600 Subject: [PATCH 04/46] Fix unit test --- .../jupytergis_lab/notebook/tests/test_api.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/tests/test_api.py b/python/jupytergis_lab/jupytergis_lab/notebook/tests/test_api.py index 8694d4af9..5f759efc3 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/tests/test_api.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/tests/test_api.py @@ -46,12 +46,11 @@ def test_remove_nonexistent_layer_raises(self): self.doc.remove_layer("foo") -def test_save_as(tmp_path): +def test_untitled_doc(tmp_path): os.chdir(tmp_path) doc = GISDocument() - assert not list(tmp_path.iterdir()) + assert len(list(tmp_path.iterdir())) == 1 - fn = "test.jgis" - doc.save_as(fn) + fn = "untitled.jGIS" assert (tmp_path / fn).is_file() From 287c74274a1cbb9fd2752bfaea9620f1996e8b43 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Mon, 14 Apr 2025 13:05:09 -0600 Subject: [PATCH 05/46] Test untitled document numbering --- .../jupytergis_lab/notebook/tests/test_api.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/tests/test_api.py b/python/jupytergis_lab/jupytergis_lab/notebook/tests/test_api.py index 5f759efc3..fee6a182c 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/tests/test_api.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/tests/test_api.py @@ -49,8 +49,10 @@ def test_remove_nonexistent_layer_raises(self): def test_untitled_doc(tmp_path): os.chdir(tmp_path) - doc = GISDocument() + GISDocument() assert len(list(tmp_path.iterdir())) == 1 + assert (tmp_path / "untitled.jGIS").is_file() - fn = "untitled.jGIS" - assert (tmp_path / fn).is_file() + GISDocument() + assert len(list(tmp_path.iterdir())) == 2 + assert (tmp_path / "untitled1.jGIS").is_file() From 6c400a5ff4b45fed4c6b018acefb154190258a32 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Mon, 14 Apr 2025 13:09:56 -0600 Subject: [PATCH 06/46] Update docstring --- python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py index 72b9ed2e3..a5fa558b9 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py @@ -43,7 +43,7 @@ class GISDocument(CommWidget): """ Create a new GISDocument object. - :param path: the path to the file that you would like to open. If not provided, a new empty document will be created. + :param path: the path to the file that you would like to open. If not provided, a new untitled document will be created. """ def __init__( From 31cd63490f93b913a751ced7895087990c2c3d9d Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Wed, 15 Jan 2025 17:57:20 -0700 Subject: [PATCH 07/46] Stub out geo_debug function with jupyterlab-sidecar --- .../jupytergis_lab/notebook/geo_debug.py | 30 +++++++++++++++++++ python/jupytergis_lab/pyproject.toml | 1 + 2 files changed, 31 insertions(+) create mode 100644 python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py b/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py new file mode 100644 index 000000000..83caad4c2 --- /dev/null +++ b/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py @@ -0,0 +1,30 @@ +from sidecar import Sidecar + +from jupytergis_lab import GISDocument + + +def geo_debug(geojson_path: str) -> None: + """Run a JupyterGIS data interaction interface alongside a Notebook.""" + # TODO: allow user to specify a different project file; + doc = GISDocument("debug.jgis") + + # TODO: Basemap choices + doc.add_raster_layer("https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}") + + # TODO: Support lots of file types + doc.add_geojson_layer(geojson_path) + + # TODO: Zoom to layer; is that feasible to do from Python? Currently not exposed in + # Python API. + + # TODO: Make map take up the whole sidebar space. + # TODO: Toolbar not visible. + # /home/shared/jupytergis/packages/base/src/toolbar/widget.tsx + # + # TODO: Activate left and right panel -- not sure how feasible yet from Python. + # Also, if using sidecar, right panel can't be displayed. Can we open a + # "native" JupyterLab pane instead of using sidecar? + + sc = Sidecar(title="JupyterGIS sidecar") + with sc: + display(doc) diff --git a/python/jupytergis_lab/pyproject.toml b/python/jupytergis_lab/pyproject.toml index 873742e75..6d4c82a45 100644 --- a/python/jupytergis_lab/pyproject.toml +++ b/python/jupytergis_lab/pyproject.toml @@ -30,6 +30,7 @@ dependencies = [ "comm>=0.1.2,<0.2.0", "pydantic>=2,<3", "jupytergis_core>=0.1.0,<1", + "sidecar>=0.7.0", ] dynamic = ["version", "description", "authors", "urls", "keywords"] license = {file = "LICENSE"} From d39290153f7a34a2c5e70350cb2d6e3adef7f8f6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 01:04:59 +0000 Subject: [PATCH 08/46] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../jupytergis_lab/jupytergis_lab/notebook/geo_debug.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py b/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py index 83caad4c2..2e71f06a1 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py @@ -5,11 +5,13 @@ def geo_debug(geojson_path: str) -> None: """Run a JupyterGIS data interaction interface alongside a Notebook.""" - # TODO: allow user to specify a different project file; + # TODO: allow user to specify a different project file; doc = GISDocument("debug.jgis") # TODO: Basemap choices - doc.add_raster_layer("https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}") + doc.add_raster_layer( + "https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}" + ) # TODO: Support lots of file types doc.add_geojson_layer(geojson_path) @@ -20,7 +22,7 @@ def geo_debug(geojson_path: str) -> None: # TODO: Make map take up the whole sidebar space. # TODO: Toolbar not visible. # /home/shared/jupytergis/packages/base/src/toolbar/widget.tsx - # + # # TODO: Activate left and right panel -- not sure how feasible yet from Python. # Also, if using sidecar, right panel can't be displayed. Can we open a # "native" JupyterLab pane instead of using sidecar? From 8c6b937353d6063cf170f761d58f77035ff3ff35 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Thu, 23 Jan 2025 09:03:05 -0700 Subject: [PATCH 09/46] WIP --- .../jupytergis_lab/jupytergis_lab/notebook/geo_debug.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py b/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py index 2e71f06a1..97e9afb1d 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py @@ -1,4 +1,4 @@ -from sidecar import Sidecar +# from jupyterlab import from jupytergis_lab import GISDocument @@ -6,6 +6,7 @@ def geo_debug(geojson_path: str) -> None: """Run a JupyterGIS data interaction interface alongside a Notebook.""" # TODO: allow user to specify a different project file; + # TODO: Just create the .jgis file doc = GISDocument("debug.jgis") # TODO: Basemap choices @@ -26,7 +27,4 @@ def geo_debug(geojson_path: str) -> None: # TODO: Activate left and right panel -- not sure how feasible yet from Python. # Also, if using sidecar, right panel can't be displayed. Can we open a # "native" JupyterLab pane instead of using sidecar? - - sc = Sidecar(title="JupyterGIS sidecar") - with sc: - display(doc) + display(doc) From dec08a6ad33b29cdd68f9008f0d36fd13fece8eb Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Mon, 27 Jan 2025 16:44:10 -0700 Subject: [PATCH 10/46] Add ESPM-157 notebook as example user of geo-debug workflow WIP --- examples/espm-157/README.md | 1 + examples/espm-157/debug.jgis | 7 + examples/espm-157/new_haven.json | 11 ++ examples/espm-157/spatial-1.ipynb | 303 ++++++++++++++++++++++++++++++ examples/espm-157/spatial-1.jGIS | 83 ++++++++ 5 files changed, 405 insertions(+) create mode 100644 examples/espm-157/README.md create mode 100644 examples/espm-157/debug.jgis create mode 100644 examples/espm-157/new_haven.json create mode 100644 examples/espm-157/spatial-1.ipynb create mode 100644 examples/espm-157/spatial-1.jGIS diff --git a/examples/espm-157/README.md b/examples/espm-157/README.md new file mode 100644 index 000000000..487391de2 --- /dev/null +++ b/examples/espm-157/README.md @@ -0,0 +1 @@ +https://espm-157.carlboettiger.info diff --git a/examples/espm-157/debug.jgis b/examples/espm-157/debug.jgis new file mode 100644 index 000000000..c77a68c0a --- /dev/null +++ b/examples/espm-157/debug.jgis @@ -0,0 +1,7 @@ +{ + "layerTree": [], + "layers": {}, + "metadata": {}, + "options": {}, + "sources": {} +} \ No newline at end of file diff --git a/examples/espm-157/new_haven.json b/examples/espm-157/new_haven.json new file mode 100644 index 000000000..08158d7e5 --- /dev/null +++ b/examples/espm-157/new_haven.json @@ -0,0 +1,11 @@ +{ +"type": "FeatureCollection", +"name": "new_haven", +"features": [ +{ "type": "Feature", "properties": { "area_id": 244, "city": "Birmingham", "state": "AL", "city_survey": true, "category": "Best", "grade": "A", "label": "A1", "residential": true, "commercial": false, "industrial": false, "fill": "#76a865" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -86.75678, 33.49754 ], [ -86.75653, 33.50176 ], [ -86.75724, 33.50179 ], [ -86.75813, 33.50088 ], [ -86.76032, 33.49846 ], [ -86.76046, 33.49818 ], [ -86.76065, 33.49731 ], [ -86.76146, 33.49581 ], [ -86.76208, 33.49529 ], [ -86.76246, 33.49489 ], [ -86.76246, 33.4945 ], [ -86.76184, 33.49402 ], [ -86.76122, 33.49402 ], [ -86.76113, 33.49371 ], [ -86.76194, 33.49264 ], [ -86.76336, 33.49172 ], [ -86.76479, 33.49089 ], [ -86.76588, 33.49057 ], [ -86.76664, 33.49006 ], [ -86.76735, 33.48931 ], [ -86.76897, 33.48839 ], [ -86.77111, 33.48697 ], [ -86.77168, 33.48617 ], [ -86.77206, 33.48554 ], [ -86.7722, 33.48494 ], [ -86.77244, 33.48455 ], [ -86.77311, 33.48419 ], [ -86.77296, 33.48379 ], [ -86.77306, 33.48344 ], [ -86.7733, 33.48308 ], [ -86.77235, 33.48201 ], [ -86.77101, 33.48142 ], [ -86.76978, 33.48074 ], [ -86.76935, 33.48007 ], [ -86.76835, 33.47876 ], [ -86.76792, 33.47832 ], [ -86.76612, 33.47741 ], [ -86.76479, 33.47682 ], [ -86.76374, 33.47606 ], [ -86.76327, 33.47511 ], [ -86.76346, 33.47456 ], [ -86.76403, 33.47356 ], [ -86.76365, 33.47293 ], [ -86.76265, 33.47202 ], [ -86.7616, 33.47154 ], [ -86.76136, 33.47186 ], [ -86.76122, 33.47226 ], [ -86.76122, 33.47309 ], [ -86.76136, 33.4736 ], [ -86.76089, 33.47428 ], [ -86.76008, 33.47479 ], [ -86.75894, 33.47527 ], [ -86.75785, 33.47579 ], [ -86.75747, 33.4761 ], [ -86.75728, 33.47686 ], [ -86.75637, 33.47741 ], [ -86.75538, 33.47785 ], [ -86.75438, 33.47781 ], [ -86.75143, 33.4809 ], [ -86.74872, 33.48312 ], [ -86.74853, 33.48399 ], [ -86.74791, 33.48455 ], [ -86.74725, 33.48467 ], [ -86.7462, 33.4849 ], [ -86.74596, 33.48514 ], [ -86.74516, 33.48483 ], [ -86.7443, 33.48494 ], [ -86.74325, 33.48558 ], [ -86.74245, 33.4857 ], [ -86.7413, 33.48558 ], [ -86.7405, 33.4855 ], [ -86.74002, 33.48451 ], [ -86.73861, 33.48514 ], [ -86.73628, 33.48697 ], [ -86.73324, 33.48891 ], [ -86.73258, 33.48954 ], [ -86.73205, 33.4903 ], [ -86.72911, 33.49279 ], [ -86.72483, 33.49747 ], [ -86.72526, 33.49799 ], [ -86.72564, 33.4985 ], [ -86.72621, 33.4987 ], [ -86.73481, 33.49402 ], [ -86.73533, 33.4922 ], [ -86.73562, 33.49133 ], [ -86.74028, 33.4895 ], [ -86.7409, 33.49026 ], [ -86.74056, 33.49105 ], [ -86.7399, 33.49145 ], [ -86.73937, 33.4918 ], [ -86.73947, 33.4922 ], [ -86.73976, 33.49267 ], [ -86.74066, 33.49299 ], [ -86.7418, 33.49303 ], [ -86.74289, 33.49295 ], [ -86.74413, 33.49244 ], [ -86.74465, 33.49307 ], [ -86.74503, 33.49406 ], [ -86.74489, 33.49525 ], [ -86.74394, 33.49581 ], [ -86.74327, 33.49656 ], [ -86.74318, 33.49743 ], [ -86.74342, 33.49866 ], [ -86.74413, 33.49878 ], [ -86.74508, 33.49922 ], [ -86.7458, 33.50016 ], [ -86.7459, 33.49704 ], [ -86.74605, 33.49662 ], [ -86.75207, 33.4921 ], [ -86.75218, 33.49175 ], [ -86.75225, 33.49044 ], [ -86.75225, 33.48779 ], [ -86.75289, 33.4869 ], [ -86.75396, 33.48764 ], [ -86.7545, 33.48883 ], [ -86.76227, 33.48892 ], [ -86.76202, 33.49192 ], [ -86.75692, 33.49579 ], [ -86.75678, 33.49754 ] ] ] ] } }, +{ "type": "Feature", "properties": { "area_id": 193, "city": "Birmingham", "state": "AL", "city_survey": true, "category": "Best", "grade": "A", "label": "A2", "residential": true, "commercial": false, "industrial": false, "fill": "#76a865" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -86.75867, 33.50933 ], [ -86.76134, 33.5124 ], [ -86.76188, 33.51196 ], [ -86.76333, 33.51183 ], [ -86.76408, 33.51156 ], [ -86.76452, 33.51101 ], [ -86.76488, 33.51027 ], [ -86.76619, 33.50985 ], [ -86.76699, 33.50963 ], [ -86.76809, 33.50888 ], [ -86.76844, 33.50841 ], [ -86.76916, 33.50787 ], [ -86.77052, 33.50742 ], [ -86.77186, 33.50653 ], [ -86.77314, 33.50574 ], [ -86.77373, 33.50532 ], [ -86.77543, 33.50311 ], [ -86.77596, 33.50276 ], [ -86.77988, 33.50078 ], [ -86.78059, 33.50103 ], [ -86.78125, 33.50093 ], [ -86.78196, 33.50071 ], [ -86.78259, 33.50034 ], [ -86.78297, 33.49969 ], [ -86.78276, 33.49833 ], [ -86.78377, 33.49823 ], [ -86.78478, 33.49786 ], [ -86.78556, 33.49739 ], [ -86.78565, 33.49692 ], [ -86.79055, 33.49427 ], [ -86.79278, 33.49474 ], [ -86.79413, 33.49377 ], [ -86.79458, 33.49308 ], [ -86.79455, 33.49253 ], [ -86.79392, 33.4904 ], [ -86.78137, 33.49603 ], [ -86.78122, 33.4937 ], [ -86.77843, 33.49367 ], [ -86.77846, 33.49392 ], [ -86.77421, 33.4938 ], [ -86.7743, 33.49538 ], [ -86.77335, 33.49543 ], [ -86.7713, 33.49679 ], [ -86.76975, 33.49707 ], [ -86.76916, 33.49754 ], [ -86.76841, 33.49786 ], [ -86.76723, 33.49776 ], [ -86.76624, 33.49786 ], [ -86.76556, 33.49826 ], [ -86.76518, 33.49883 ], [ -86.76387, 33.50026 ], [ -86.76339, 33.50076 ], [ -86.7625, 33.50115 ], [ -86.76119, 33.50162 ], [ -86.75947, 33.50371 ], [ -86.75804, 33.50326 ], [ -86.75742, 33.50338 ], [ -86.75689, 33.50363 ], [ -86.76093, 33.50807 ], [ -86.75867, 33.50933 ] ] ] ] } }, +{ "type": "Feature", "properties": { "area_id": 206, "city": "Birmingham", "state": "AL", "city_survey": true, "category": "Best", "grade": "A", "label": "A3", "residential": true, "commercial": false, "industrial": false, "fill": "#76a865" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -86.75678, 33.49754 ], [ -86.75692, 33.49579 ], [ -86.76202, 33.49192 ], [ -86.76227, 33.48892 ], [ -86.7545, 33.48883 ], [ -86.75396, 33.48764 ], [ -86.75289, 33.4869 ], [ -86.75225, 33.48779 ], [ -86.75225, 33.49044 ], [ -86.75218, 33.49175 ], [ -86.75207, 33.4921 ], [ -86.74605, 33.49662 ], [ -86.7459, 33.49704 ], [ -86.7458, 33.50016 ], [ -86.74605, 33.5009 ], [ -86.75196, 33.50135 ], [ -86.75678, 33.49754 ] ] ] ] } }, +{ "type": "Feature", "properties": { "area_id": 203, "city": "Birmingham", "state": "AL", "city_survey": true, "category": "Still Desirable", "grade": "B", "label": "B1", "residential": true, "commercial": false, "industrial": false, "fill": "#7cb5bd" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -86.80111, 33.48071 ], [ -86.80505, 33.48044 ], [ -86.8069, 33.47782 ], [ -86.81346, 33.4777 ], [ -86.81289, 33.47137 ], [ -86.81546, 33.46973 ], [ -86.80929, 33.46479 ], [ -86.80491, 33.46503 ], [ -86.79564, 33.46801 ], [ -86.79503, 33.46765 ], [ -86.79179, 33.46815 ], [ -86.791, 33.46857 ], [ -86.79068, 33.46946 ], [ -86.79004, 33.46997 ], [ -86.78884, 33.47047 ], [ -86.78784, 33.4714 ], [ -86.78781, 33.47169 ], [ -86.78809, 33.47193 ], [ -86.78898, 33.47205 ], [ -86.78684, 33.47342 ], [ -86.78563, 33.4736 ], [ -86.7847, 33.47351 ], [ -86.78367, 33.47312 ], [ -86.78246, 33.47244 ], [ -86.78182, 33.47125 ], [ -86.78189, 33.47059 ], [ -86.78296, 33.4697 ], [ -86.78303, 33.46911 ], [ -86.78157, 33.46679 ], [ -86.77237, 33.47039 ], [ -86.77119, 33.47149 ], [ -86.76937, 33.47235 ], [ -86.76706, 33.4736 ], [ -86.76956, 33.47449 ], [ -86.7697, 33.4758 ], [ -86.77819, 33.47621 ], [ -86.77822, 33.47978 ], [ -86.77651, 33.47969 ], [ -86.77651, 33.48314 ], [ -86.77986, 33.48151 ], [ -86.78001, 33.48368 ], [ -86.7829, 33.48354 ], [ -86.78409, 33.48361 ], [ -86.78489, 33.48282 ], [ -86.78706, 33.48213 ], [ -86.78694, 33.47876 ], [ -86.78697, 33.47635 ], [ -86.79232, 33.4764 ], [ -86.79342, 33.47506 ], [ -86.80049, 33.47464 ], [ -86.80067, 33.47662 ], [ -86.79874, 33.47722 ], [ -86.79811, 33.4789 ], [ -86.80058, 33.4795 ], [ -86.80099, 33.48024 ], [ -86.80111, 33.48071 ] ] ] ] } }, +{ "type": "Feature", "properties": { "area_id": 189, "city": "Birmingham", "state": "AL", "city_survey": true, "category": "Still Desirable", "grade": "B", "label": "B10", "residential": true, "commercial": false, "industrial": false, "fill": "#7cb5bd" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -86.74923, 33.53333 ], [ -86.74971, 33.5333 ], [ -86.7532, 33.53148 ], [ -86.75446, 33.53233 ], [ -86.75539, 33.53201 ], [ -86.75605, 33.53123 ], [ -86.75473, 33.53029 ], [ -86.75473, 33.52977 ], [ -86.75437, 33.52938 ], [ -86.75389, 33.52908 ], [ -86.74916, 33.53081 ], [ -86.74923, 33.53333 ] ] ] ] } } +] +} diff --git a/examples/espm-157/spatial-1.ipynb b/examples/espm-157/spatial-1.ipynb new file mode 100644 index 000000000..2539af8b9 --- /dev/null +++ b/examples/espm-157/spatial-1.ipynb @@ -0,0 +1,303 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "c3f4096f-cbd3-43e8-a986-520681f03581", + "metadata": {}, + "source": [ + "# ESPM 157 - Intro to Spatial Data\n", + "\n", + "" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "execution_state": "idle", + "id": "b76ae1f0-334e-41c0-9533-407c879b4ad6", + "metadata": {}, + "outputs": [], + "source": [ + "import ibis\n", + "\n", + "con = ibis.duckdb.connect()" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "execution_state": "idle", + "id": "837aa4e0-5eeb-48c1-97ce-3f8706503911", + "metadata": {}, + "outputs": [], + "source": [ + "# IOException: IO Error: GDAL Error (11): CURL error: error setting certificate file: /etc/pki/tls/certs/ca-bundle.crt\n", + "# redlines = con.read_geo(\"/vsicurl/https://dsl.richmond.edu/panorama/redlining/static/mappinginequality.gpkg\")\n", + "\n", + "redlines = con.read_geo(\"./mappinginequality.gpkg\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "execution_state": "idle", + "id": "5c91dfaa-075c-401f-adeb-18bed678103b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
city
0Montgomery
1Fresno
2Colorado Springs
3DeLand
4Orlando
5Grand Rapids
6Fargo
7Atlantic City
8Atchison
9Brookline
\n", + "
" + ], + "text/plain": [ + " city\n", + "0 Montgomery\n", + "1 Fresno\n", + "2 Colorado Springs\n", + "3 DeLand\n", + "4 Orlando\n", + "5 Grand Rapids\n", + "6 Fargo\n", + "7 Atlantic City\n", + "8 Atchison\n", + "9 Brookline" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "redlines.select(redlines.city).distinct().head(10).execute()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "execution_state": "idle", + "id": "4d3319fa-b3b4-4931-b073-98b649e41b65", + "metadata": {}, + "outputs": [], + "source": [ + "# city = redlines.filter(_.city == \"Birmingham\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "execution_state": "idle", + "id": "c6b07243-9e7f-4b07-94b3-52e78019a59e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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4Wapqak4fvw4ZsyY0XRfZGQkvLy88Mknn6C+vh5Xr17FJ598gp49e6Jbt27GfqkWTcz3wRjtWgsx3wcAOH36NP72t79h9erVt9z4jzpI7NRE0paXlyfEx8cLMplMUCgUQkBAQLMunAULFgj29vZCZGSksGnTJmH//v3CyJEjhcjISEGr1bbZbnl5uZCYmCgAEOzs7ASVSiWkpKQ0HX/nnXeEHj16tHiej4+PsHTpUuO+SAkw1ftws9mzZws9e/ZscX96eroQFhYmyOVyQS6XC5GRkUJOTo7RXpuUiPU+GKNdayLW+1BXVyfExsYKn3/+uSAIgrB9+3ZeaTEyhhZq4ZVXXhEA3PJ25swZQa/XCxMmTBDGjh0r7NmzR0hNTRVmz54tdOnSRSgsLBQE4Vq4ANAscJSUlAhyuVzYtGlTmzU899xzwoABA4QtW7YIx48fF+bNmyeo1WohLS2tqV1rDy2W8D7cUFtbK6jVamHx4sUt7h8wYIAwbdo04dChQ8L+/fuF++67T4iJiRFqa2uN+xciEim8D51tVwqk8D78+c9/Fh566KGm7xlajI+hhVooKSkRzpw5c8ubVqsVtmzZIsjlckGj0TR7fnh4uLBgwQJBEATh008/FQAIeXl5zR7j6+srfPzxx62ePysrSwAgnDx5stn9I0eOFGbOnCkIgiB88skngru7e7PjDQ0NgkKhEL777rtOvX5LIfb7cLPVq1cL9vb2QklJSbP7V6xYIfj6+go6na7pPq1WK7i4uAhfffVVR1+6RZHC+9DZdqVACu9DXFycIJfLBYVCISgUCkEulwsABIVCIbzxxhud/BsgQRAEOxP0OJHE+fj4wMfH57aPq62tBYAW/bZyuRx6vR4AMGTIEABAZmYmgoKCAFybqnzlyhV07drVoHYVCkVTuwkJCaioqEBqampTP/W2bdug1+sxcODAdr1OSyf2+3CzTz75BBMmTGhRT21tLeRyOWQyWbPzymSypnNLnRTeh862KwVSeB++/fZbXL16ten7w4cP44knnsDu3bsRFhZ223apHcROTSRdly9fFry8vIR7771XOH78uJCZmSm8+OKLgr29vXD8+PGmxyUnJwsxMTHC3r17hfT0dOGee+4RoqOjm6bE5ufnC5GRkcLBgwcFQRCE+vp6ITw8XLjzzjuFgwcPCllZWcLixYsFmUwm/Pzzz03tJiUlCX369BEOHjwo7NmzR4iIiLDJKc+meh9uOHfunCCTyYSNGze2OPeZM2cER0dHYfbs2cLp06eFkydPCo8++qigVqubLsXbCjHfh/a0ayvEfh9uxu4h42NooU45fPiwkJiYKHh6egpubm7CoEGDhF9++aXZYzQajfDEE08I7u7ugqenpzBp0qRmU2QvXLggABC2b9/edN/Zs2eFe++9V/D19RVcXFyE2NjYFlOgS0tLhcmTJwtKpVJQqVTC448/3up6CbbAVO+DIAjC3LlzheDg4GZdQDf79ddfhSFDhghqtVrw8PAQRowYIezfv9/or1EKxHwfbteuLRHzfbgZQ4vxyQRBEMS80kNERETUHpxETkRERJLA0EJERESSwNBCREREksDQQkRERJLA0EJERESSwNBCREREksDQQkRERJLA0EJERESSwNBCREREksDQQkRERJLA0EJERESSwNBCREREkvD/K3G3EXBzbMkAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "city_gdf = redlines.head().execute()\n", + "city_gdf.plot()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "execution_state": "idle", + "id": "34f7f4d6-28d6-4716-8e03-ac32c6ae3bb7", + "metadata": {}, + "outputs": [], + "source": [ + "# city_gdf = city.head().execute()\n", + "# city_gdf.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "d37a610a-1444-43a3-900f-dfe16a890ab9", + "metadata": {}, + "source": [ + "## OK, but what about spatial context?\n", + "\n", + "I want to explore this data more interactively." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "execution_state": "idle", + "id": "94875994-926a-46b4-be29-be7c676a9905", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.ywidget-view+json": { + "model_id": "573317eb5d694bb78052fab671454827", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from jupytergis_lab import GISDocument\n", + "\n", + "doc = GISDocument(\"./debug.jgis\")\n", + "doc" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "execution_state": "idle", + "id": "642e9281-d9d5-4f35-a7f6-65bf1096cb1f", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/mfisher/.local/share/micromamba/envs/jupytergis-dev/lib/python3.12/site-packages/pyogrio/geopandas.py:662: UserWarning: 'crs' was not provided. The output dataset will not have projection information defined and may not be usable in other systems.\n", + " write(\n" + ] + } + ], + "source": [ + "city_gdf.to_file(\"new_haven.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "execution_state": "idle", + "id": "6e68aaac-dfc0-42d8-a3b9-05fce59524b2", + "metadata": {}, + "outputs": [ + { + "ename": "ModuleNotFoundError", + "evalue": "No module named 'jupytergis'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[9], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mjupytergis\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mnotebook\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mgeo_debug\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m geo_debug\n\u001b[1;32m 4\u001b[0m geo_debug(city_gdf)\n", + "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'jupytergis'" + ] + } + ], + "source": [ + "from jupytergis.notebook.geo_debug import geo_debug\n", + "\n", + "\n", + "geo_debug(city_gdf)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/espm-157/spatial-1.jGIS b/examples/espm-157/spatial-1.jGIS new file mode 100644 index 000000000..55f0130e1 --- /dev/null +++ b/examples/espm-157/spatial-1.jGIS @@ -0,0 +1,83 @@ +{ + "layerTree": [ + "3d605d1b-7a7b-4e8e-be88-75ac4e5d31c7", + "01ed0c0a-d3d2-441c-9d46-fbd1bb191e38", + "31b41fa3-c8a5-4e25-9853-73b4f788da0c" + ], + "layers": { + "01ed0c0a-d3d2-441c-9d46-fbd1bb191e38": { + "name": "Custom GeoJSON Layer", + "parameters": { + "opacity": 1.0, + "source": "5d31501d-e03a-4798-bcae-a37a5fbe7237", + "type": "line" + }, + "type": "VectorLayer", + "visible": true + }, + "31b41fa3-c8a5-4e25-9853-73b4f788da0c": { + "name": "USGS.USTopo Layer", + "parameters": { + "source": "a21cb39a-9174-4cc4-9362-1d46f35d56b8" + }, + "type": "RasterLayer", + "visible": true + }, + "3d605d1b-7a7b-4e8e-be88-75ac4e5d31c7": { + "name": "OpenStreetMap.Mapnik Layer", + "parameters": { + "source": "72f468ff-6b37-4893-986a-d7bae1f16242" + }, + "type": "RasterLayer", + "visible": true + } + }, + "metadata": {}, + "options": { + "bearing": 0.0, + "extent": [ + -8451224.227063052, + 3855330.008457263, + -7419889.364430341, + 5816290.181194804 + ], + "latitude": 39.79233083947287, + "longitude": -71.28631957617792, + "pitch": 0.0, + "projection": "EPSG:3857", + "zoom": 6.265960317603158 + }, + "sources": { + "5d31501d-e03a-4798-bcae-a37a5fbe7237": { + "name": "Custom GeoJSON Layer Source", + "parameters": { + "path": "new_haven.json" + }, + "type": "GeoJSONSource" + }, + "72f468ff-6b37-4893-986a-d7bae1f16242": { + "name": "OpenStreetMap.Mapnik", + "parameters": { + "attribution": "(C) OpenStreetMap contributors", + "maxZoom": 19.0, + "minZoom": 0.0, + "provider": "OpenStreetMap", + "url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + "urlParameters": {} + }, + "type": "RasterSource" + }, + "a21cb39a-9174-4cc4-9362-1d46f35d56b8": { + "name": "USGS.USTopo", + "parameters": { + "attribution": "Tiles courtesy of the U.S. Geological Survey", + "maxZoom": 20.0, + "minZoom": 0.0, + "provider": "USGS", + "url": "https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}", + "urlParameters": {} + }, + "type": "RasterSource" + } + } +} From e6a05700c03bd4083e648b183aa64233799d944b Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Mon, 27 Jan 2025 18:57:08 -0700 Subject: [PATCH 11/46] Use correct .jGIS extension --- examples/espm-157/debug.jGIS | 550 ++++++++++++++++++ examples/espm-157/debug.jgis | 7 - .../jupytergis_lab/notebook/geo_debug.py | 2 +- 3 files changed, 551 insertions(+), 8 deletions(-) create mode 100644 examples/espm-157/debug.jGIS delete mode 100644 examples/espm-157/debug.jgis diff --git a/examples/espm-157/debug.jGIS b/examples/espm-157/debug.jGIS new file mode 100644 index 000000000..bf9365ce5 --- /dev/null +++ b/examples/espm-157/debug.jGIS @@ -0,0 +1,550 @@ +{ + "layerTree": [ + "72c882c3-927c-4876-b561-fc6929057571", + "4722d40c-e025-404e-a28f-a960c9e5549e" + ], + "layers": { + "4722d40c-e025-404e-a28f-a960c9e5549e": { + "filters": { + "appliedFilters": [ + { + "feature": null, + "operator": null, + "value": null + } + ], + "logicalOp": null + }, + "name": "GeoJSON Layer", + "parameters": { + "color": null, + "opacity": 1.0, + "source": "2db53792-10a5-4c0d-8c38-417877eaa4ed", + "sourceLayer": null, + "symbologyState": null, + "type": "line" + }, + "type": "VectorLayer", + "visible": true + }, + "72c882c3-927c-4876-b561-fc6929057571": { + "filters": null, + "name": "Raster Layer", + "parameters": { + "opacity": 1.0, + "source": "8f3f22ce-de8b-4a2b-8ca2-c371e717eebc" + }, + "type": "RasterLayer", + "visible": true + } + }, + "metadata": {}, + "options": { + "bearing": 0.0, + "extent": [ + -8119789.892274434, + 5062391.865552574, + -8111810.8138090465, + 5067305.0912085995 + ], + "latitude": 41.35501710777166, + "longitude": -72.90547500000002, + "pitch": 0.0, + "projection": "EPSG:3857", + "zoom": 14.281457379038391 + }, + "sources": { + "2db53792-10a5-4c0d-8c38-417877eaa4ed": { + "name": "GeoJSON Layer Source", + "parameters": { + "data": { + "bbox": null, + "features": [ + { + "bbox": null, + "geometry": { + "bbox": null, + "coordinates": [ + [ + [ + [ + -72.9, + 41.36885 + ], + [ + -72.90325, + 41.36999 + ], + [ + -72.90644, + 41.37108 + ], + [ + -72.90804, + 41.36644 + ], + [ + -72.90102, + 41.36493 + ], + [ + -72.89904, + 41.36851 + ], + [ + -72.9, + 41.36885 + ] + ] + ] + ], + "type": "MultiPolygon" + }, + "id": null, + "properties": { + "area_id": 3569.0, + "category": "Best", + "city": "New Haven", + "city_survey": true, + "commercial": false, + "fill": "#76a865", + "grade": "A", + "industrial": false, + "label": "A1", + "residential": true, + "state": "CT" + }, + "type": "Feature" + }, + { + "bbox": null, + "geometry": { + "bbox": null, + "coordinates": [ + [ + [ + [ + -72.89401, + 41.36331 + ], + [ + -72.89256, + 41.36574 + ], + [ + -72.89904, + 41.36851 + ], + [ + -72.90102, + 41.36493 + ], + [ + -72.89401, + 41.36331 + ] + ] + ] + ], + "type": "MultiPolygon" + }, + "id": null, + "properties": { + "area_id": 3568.0, + "category": "Best", + "city": "New Haven", + "city_survey": true, + "commercial": false, + "fill": "#76a865", + "grade": "A", + "industrial": false, + "label": "A2", + "residential": true, + "state": "CT" + }, + "type": "Feature" + }, + { + "bbox": null, + "geometry": { + "bbox": null, + "coordinates": [ + [ + [ + [ + -72.90981, + 41.35949 + ], + [ + -72.90284, + 41.35795 + ], + [ + -72.90178, + 41.36076 + ], + [ + -72.90132, + 41.36172 + ], + [ + -72.90069, + 41.36266 + ], + [ + -72.90062, + 41.36305 + ], + [ + -72.90078, + 41.36333 + ], + [ + -72.90073, + 41.36396 + ], + [ + -72.90102, + 41.36493 + ], + [ + -72.90804, + 41.36644 + ], + [ + -72.90644, + 41.37108 + ], + [ + -72.90768, + 41.37158 + ], + [ + -72.90794, + 41.37081 + ], + [ + -72.90844, + 41.37044 + ], + [ + -72.90842, + 41.36985 + ], + [ + -72.90868, + 41.36905 + ], + [ + -72.90935, + 41.3684 + ], + [ + -72.91134, + 41.36756 + ], + [ + -72.9125, + 41.36733 + ], + [ + -72.91544, + 41.36673 + ], + [ + -72.91561, + 41.36632 + ], + [ + -72.91753, + 41.36557 + ], + [ + -72.91818, + 41.36519 + ], + [ + -72.91832, + 41.36462 + ], + [ + -72.91839, + 41.3642 + ], + [ + -72.91383, + 41.36306 + ], + [ + -72.91114, + 41.36279 + ], + [ + -72.90932, + 41.36237 + ], + [ + -72.9096, + 41.35969 + ], + [ + -72.90981, + 41.35949 + ] + ] + ] + ], + "type": "MultiPolygon" + }, + "id": null, + "properties": { + "area_id": 3566.0, + "category": "Best", + "city": "New Haven", + "city_survey": true, + "commercial": false, + "fill": "#76a865", + "grade": "A", + "industrial": false, + "label": "A3", + "residential": true, + "state": "CT" + }, + "type": "Feature" + }, + { + "bbox": null, + "geometry": { + "bbox": null, + "coordinates": [ + [ + [ + [ + -72.90284, + 41.35795 + ], + [ + -72.8999, + 41.35712 + ], + [ + -72.89788, + 41.36147 + ], + [ + -72.89564, + 41.36069 + ], + [ + -72.89401, + 41.36331 + ], + [ + -72.90102, + 41.36493 + ], + [ + -72.90073, + 41.36396 + ], + [ + -72.90078, + 41.36333 + ], + [ + -72.90062, + 41.36305 + ], + [ + -72.90069, + 41.36266 + ], + [ + -72.90132, + 41.36172 + ], + [ + -72.90178, + 41.36076 + ], + [ + -72.90284, + 41.35795 + ] + ] + ] + ], + "type": "MultiPolygon" + }, + "id": null, + "properties": { + "area_id": 3567.0, + "category": "Best", + "city": "New Haven", + "city_survey": true, + "commercial": false, + "fill": "#76a865", + "grade": "A", + "industrial": false, + "label": "A4", + "residential": true, + "state": "CT" + }, + "type": "Feature" + }, + { + "bbox": null, + "geometry": { + "bbox": null, + "coordinates": [ + [ + [ + [ + -72.89801, + 41.3493 + ], + [ + -72.89883, + 41.34937 + ], + [ + -72.9027, + 41.35013 + ], + [ + -72.90569, + 41.34575 + ], + [ + -72.90976, + 41.34024 + ], + [ + -72.90956, + 41.33932 + ], + [ + -72.90925, + 41.33868 + ], + [ + -72.90892, + 41.33903 + ], + [ + -72.90857, + 41.33906 + ], + [ + -72.90721, + 41.33878 + ], + [ + -72.90567, + 41.34024 + ], + [ + -72.90249, + 41.33966 + ], + [ + -72.90192, + 41.33845 + ], + [ + -72.89924, + 41.34017 + ], + [ + -72.89837, + 41.34152 + ], + [ + -72.90214, + 41.34213 + ], + [ + -72.90237, + 41.3438 + ], + [ + -72.90121, + 41.34359 + ], + [ + -72.90016, + 41.34354 + ], + [ + -72.89875, + 41.34364 + ], + [ + -72.89792, + 41.34374 + ], + [ + -72.89748, + 41.34626 + ], + [ + -72.89735, + 41.34745 + ], + [ + -72.89731, + 41.34854 + ], + [ + -72.89758, + 41.34911 + ], + [ + -72.89801, + 41.3493 + ] + ] + ] + ], + "type": "MultiPolygon" + }, + "id": null, + "properties": { + "area_id": 3564.0, + "category": "Best", + "city": "New Haven", + "city_survey": true, + "commercial": false, + "fill": "#76a865", + "grade": "A", + "industrial": false, + "label": "A5", + "residential": true, + "state": "CT" + }, + "type": "Feature" + } + ], + "type": "FeatureCollection" + }, + "path": null, + "valid": null + }, + "type": "GeoJSONSource" + }, + "8f3f22ce-de8b-4a2b-8ca2-c371e717eebc": { + "name": "Raster Layer Source", + "parameters": { + "attribution": "", + "bounds": [], + "htmlAttribution": "", + "maxZoom": 24.0, + "minZoom": 0.0, + "provider": "", + "url": "https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}", + "urlParameters": {} + }, + "type": "RasterSource" + } + } +} \ No newline at end of file diff --git a/examples/espm-157/debug.jgis b/examples/espm-157/debug.jgis deleted file mode 100644 index c77a68c0a..000000000 --- a/examples/espm-157/debug.jgis +++ /dev/null @@ -1,7 +0,0 @@ -{ - "layerTree": [], - "layers": {}, - "metadata": {}, - "options": {}, - "sources": {} -} \ No newline at end of file diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py b/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py index 97e9afb1d..cbebee2e0 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py @@ -7,7 +7,7 @@ def geo_debug(geojson_path: str) -> None: """Run a JupyterGIS data interaction interface alongside a Notebook.""" # TODO: allow user to specify a different project file; # TODO: Just create the .jgis file - doc = GISDocument("debug.jgis") + doc = GISDocument("debug.jGIS") # TODO: Basemap choices doc.add_raster_layer( From 2e56f0c3e6b4ab33bde80d79e4adcb9842aedfc7 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Mon, 27 Jan 2025 19:02:21 -0700 Subject: [PATCH 12/46] Dunno, rebase me later --- examples/espm-157/debug.jGIS | 504 +++++++++++++++++++++++++++++- examples/espm-157/new_haven.json | 10 +- examples/espm-157/spatial-1.ipynb | 140 ++++----- examples/espm-157/spatial-1.jGIS | 10 +- 4 files changed, 563 insertions(+), 101 deletions(-) diff --git a/examples/espm-157/debug.jGIS b/examples/espm-157/debug.jGIS index bf9365ce5..c5891b8ee 100644 --- a/examples/espm-157/debug.jGIS +++ b/examples/espm-157/debug.jGIS @@ -42,18 +42,496 @@ "options": { "bearing": 0.0, "extent": [ - -8119789.892274434, - 5062391.865552574, - -8111810.8138090465, - 5067305.0912085995 + -8119381.111899852, + 5062686.6590919355, + -8112203.87186153, + 5067026.019991337 ], - "latitude": 41.35501710777166, - "longitude": -72.90547500000002, + "latitude": 41.35507011575021, + "longitude": -72.90540438198882, "pitch": 0.0, "projection": "EPSG:3857", "zoom": 14.281457379038391 }, "sources": { + "0703832e-dbc3-474d-85e7-d378a637512b": { + "name": "GeoJSON Layer Source", + "parameters": { + "data": { + "bbox": null, + "features": [ + { + "bbox": null, + "geometry": { + "bbox": null, + "coordinates": [ + [ + [ + [ + -72.9, + 41.36885 + ], + [ + -72.90325, + 41.36999 + ], + [ + -72.90644, + 41.37108 + ], + [ + -72.90804, + 41.36644 + ], + [ + -72.90102, + 41.36493 + ], + [ + -72.89904, + 41.36851 + ], + [ + -72.9, + 41.36885 + ] + ] + ] + ], + "type": "MultiPolygon" + }, + "id": null, + "properties": { + "area_id": 3569.0, + "category": "Best", + "city": "New Haven", + "city_survey": true, + "commercial": false, + "fill": "#76a865", + "grade": "A", + "industrial": false, + "label": "A1", + "residential": true, + "state": "CT" + }, + "type": "Feature" + }, + { + "bbox": null, + "geometry": { + "bbox": null, + "coordinates": [ + [ + [ + [ + -72.89401, + 41.36331 + ], + [ + -72.89256, + 41.36574 + ], + [ + -72.89904, + 41.36851 + ], + [ + -72.90102, + 41.36493 + ], + [ + -72.89401, + 41.36331 + ] + ] + ] + ], + "type": "MultiPolygon" + }, + "id": null, + "properties": { + "area_id": 3568.0, + "category": "Best", + "city": "New Haven", + "city_survey": true, + "commercial": false, + "fill": "#76a865", + "grade": "A", + "industrial": false, + "label": "A2", + "residential": true, + "state": "CT" + }, + "type": "Feature" + }, + { + "bbox": null, + "geometry": { + "bbox": null, + "coordinates": [ + [ + [ + [ + -72.90981, + 41.35949 + ], + [ + -72.90284, + 41.35795 + ], + [ + -72.90178, + 41.36076 + ], + [ + -72.90132, + 41.36172 + ], + [ + -72.90069, + 41.36266 + ], + [ + -72.90062, + 41.36305 + ], + [ + -72.90078, + 41.36333 + ], + [ + -72.90073, + 41.36396 + ], + [ + -72.90102, + 41.36493 + ], + [ + -72.90804, + 41.36644 + ], + [ + -72.90644, + 41.37108 + ], + [ + -72.90768, + 41.37158 + ], + [ + -72.90794, + 41.37081 + ], + [ + -72.90844, + 41.37044 + ], + [ + -72.90842, + 41.36985 + ], + [ + -72.90868, + 41.36905 + ], + [ + -72.90935, + 41.3684 + ], + [ + -72.91134, + 41.36756 + ], + [ + -72.9125, + 41.36733 + ], + [ + -72.91544, + 41.36673 + ], + [ + -72.91561, + 41.36632 + ], + [ + -72.91753, + 41.36557 + ], + [ + -72.91818, + 41.36519 + ], + [ + -72.91832, + 41.36462 + ], + [ + -72.91839, + 41.3642 + ], + [ + -72.91383, + 41.36306 + ], + [ + -72.91114, + 41.36279 + ], + [ + -72.90932, + 41.36237 + ], + [ + -72.9096, + 41.35969 + ], + [ + -72.90981, + 41.35949 + ] + ] + ] + ], + "type": "MultiPolygon" + }, + "id": null, + "properties": { + "area_id": 3566.0, + "category": "Best", + "city": "New Haven", + "city_survey": true, + "commercial": false, + "fill": "#76a865", + "grade": "A", + "industrial": false, + "label": "A3", + "residential": true, + "state": "CT" + }, + "type": "Feature" + }, + { + "bbox": null, + "geometry": { + "bbox": null, + "coordinates": [ + [ + [ + [ + -72.90284, + 41.35795 + ], + [ + -72.8999, + 41.35712 + ], + [ + -72.89788, + 41.36147 + ], + [ + -72.89564, + 41.36069 + ], + [ + -72.89401, + 41.36331 + ], + [ + -72.90102, + 41.36493 + ], + [ + -72.90073, + 41.36396 + ], + [ + -72.90078, + 41.36333 + ], + [ + -72.90062, + 41.36305 + ], + [ + -72.90069, + 41.36266 + ], + [ + -72.90132, + 41.36172 + ], + [ + -72.90178, + 41.36076 + ], + [ + -72.90284, + 41.35795 + ] + ] + ] + ], + "type": "MultiPolygon" + }, + "id": null, + "properties": { + "area_id": 3567.0, + "category": "Best", + "city": "New Haven", + "city_survey": true, + "commercial": false, + "fill": "#76a865", + "grade": "A", + "industrial": false, + "label": "A4", + "residential": true, + "state": "CT" + }, + "type": "Feature" + }, + { + "bbox": null, + "geometry": { + "bbox": null, + "coordinates": [ + [ + [ + [ + -72.89801, + 41.3493 + ], + [ + -72.89883, + 41.34937 + ], + [ + -72.9027, + 41.35013 + ], + [ + -72.90569, + 41.34575 + ], + [ + -72.90976, + 41.34024 + ], + [ + -72.90956, + 41.33932 + ], + [ + -72.90925, + 41.33868 + ], + [ + -72.90892, + 41.33903 + ], + [ + -72.90857, + 41.33906 + ], + [ + -72.90721, + 41.33878 + ], + [ + -72.90567, + 41.34024 + ], + [ + -72.90249, + 41.33966 + ], + [ + -72.90192, + 41.33845 + ], + [ + -72.89924, + 41.34017 + ], + [ + -72.89837, + 41.34152 + ], + [ + -72.90214, + 41.34213 + ], + [ + -72.90237, + 41.3438 + ], + [ + -72.90121, + 41.34359 + ], + [ + -72.90016, + 41.34354 + ], + [ + -72.89875, + 41.34364 + ], + [ + -72.89792, + 41.34374 + ], + [ + -72.89748, + 41.34626 + ], + [ + -72.89735, + 41.34745 + ], + [ + -72.89731, + 41.34854 + ], + [ + -72.89758, + 41.34911 + ], + [ + -72.89801, + 41.3493 + ] + ] + ] + ], + "type": "MultiPolygon" + }, + "id": null, + "properties": { + "area_id": 3564.0, + "category": "Best", + "city": "New Haven", + "city_survey": true, + "commercial": false, + "fill": "#76a865", + "grade": "A", + "industrial": false, + "label": "A5", + "residential": true, + "state": "CT" + }, + "type": "Feature" + } + ], + "type": "FeatureCollection" + }, + "path": null, + "valid": null + }, + "type": "GeoJSONSource" + }, "2db53792-10a5-4c0d-8c38-417877eaa4ed": { "name": "GeoJSON Layer Source", "parameters": { @@ -545,6 +1023,20 @@ "urlParameters": {} }, "type": "RasterSource" + }, + "99f813a4-730a-4682-9a01-6711d0048330": { + "name": "Raster Layer Source", + "parameters": { + "attribution": "", + "bounds": [], + "htmlAttribution": "", + "maxZoom": 24.0, + "minZoom": 0.0, + "provider": "", + "url": "https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}", + "urlParameters": {} + }, + "type": "RasterSource" } } } \ No newline at end of file diff --git a/examples/espm-157/new_haven.json b/examples/espm-157/new_haven.json index 08158d7e5..7fe3f00b0 100644 --- a/examples/espm-157/new_haven.json +++ b/examples/espm-157/new_haven.json @@ -2,10 +2,10 @@ "type": "FeatureCollection", "name": "new_haven", "features": [ -{ "type": "Feature", "properties": { "area_id": 244, "city": "Birmingham", "state": "AL", "city_survey": true, "category": "Best", "grade": "A", "label": "A1", "residential": true, "commercial": false, "industrial": false, "fill": "#76a865" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -86.75678, 33.49754 ], [ -86.75653, 33.50176 ], [ -86.75724, 33.50179 ], [ -86.75813, 33.50088 ], [ -86.76032, 33.49846 ], [ -86.76046, 33.49818 ], [ -86.76065, 33.49731 ], [ -86.76146, 33.49581 ], [ -86.76208, 33.49529 ], [ -86.76246, 33.49489 ], [ -86.76246, 33.4945 ], [ -86.76184, 33.49402 ], [ -86.76122, 33.49402 ], [ -86.76113, 33.49371 ], [ -86.76194, 33.49264 ], [ -86.76336, 33.49172 ], [ -86.76479, 33.49089 ], [ -86.76588, 33.49057 ], [ -86.76664, 33.49006 ], [ -86.76735, 33.48931 ], [ -86.76897, 33.48839 ], [ -86.77111, 33.48697 ], [ -86.77168, 33.48617 ], [ -86.77206, 33.48554 ], [ -86.7722, 33.48494 ], [ -86.77244, 33.48455 ], [ -86.77311, 33.48419 ], [ -86.77296, 33.48379 ], [ -86.77306, 33.48344 ], [ -86.7733, 33.48308 ], [ -86.77235, 33.48201 ], [ -86.77101, 33.48142 ], [ -86.76978, 33.48074 ], [ -86.76935, 33.48007 ], [ -86.76835, 33.47876 ], [ -86.76792, 33.47832 ], [ -86.76612, 33.47741 ], [ -86.76479, 33.47682 ], [ -86.76374, 33.47606 ], [ -86.76327, 33.47511 ], [ -86.76346, 33.47456 ], [ -86.76403, 33.47356 ], [ -86.76365, 33.47293 ], [ -86.76265, 33.47202 ], [ -86.7616, 33.47154 ], [ -86.76136, 33.47186 ], [ -86.76122, 33.47226 ], [ -86.76122, 33.47309 ], [ -86.76136, 33.4736 ], [ -86.76089, 33.47428 ], [ -86.76008, 33.47479 ], [ -86.75894, 33.47527 ], [ -86.75785, 33.47579 ], [ -86.75747, 33.4761 ], [ -86.75728, 33.47686 ], [ -86.75637, 33.47741 ], [ -86.75538, 33.47785 ], [ -86.75438, 33.47781 ], [ -86.75143, 33.4809 ], [ -86.74872, 33.48312 ], [ -86.74853, 33.48399 ], [ -86.74791, 33.48455 ], [ -86.74725, 33.48467 ], [ -86.7462, 33.4849 ], [ -86.74596, 33.48514 ], [ -86.74516, 33.48483 ], [ -86.7443, 33.48494 ], [ -86.74325, 33.48558 ], [ -86.74245, 33.4857 ], [ -86.7413, 33.48558 ], [ -86.7405, 33.4855 ], [ -86.74002, 33.48451 ], [ -86.73861, 33.48514 ], [ -86.73628, 33.48697 ], [ -86.73324, 33.48891 ], [ -86.73258, 33.48954 ], [ -86.73205, 33.4903 ], [ -86.72911, 33.49279 ], [ -86.72483, 33.49747 ], [ -86.72526, 33.49799 ], [ -86.72564, 33.4985 ], [ -86.72621, 33.4987 ], [ -86.73481, 33.49402 ], [ -86.73533, 33.4922 ], [ -86.73562, 33.49133 ], [ -86.74028, 33.4895 ], [ -86.7409, 33.49026 ], [ -86.74056, 33.49105 ], [ -86.7399, 33.49145 ], [ -86.73937, 33.4918 ], [ -86.73947, 33.4922 ], [ -86.73976, 33.49267 ], [ -86.74066, 33.49299 ], [ -86.7418, 33.49303 ], [ -86.74289, 33.49295 ], [ -86.74413, 33.49244 ], [ -86.74465, 33.49307 ], [ -86.74503, 33.49406 ], [ -86.74489, 33.49525 ], [ -86.74394, 33.49581 ], [ -86.74327, 33.49656 ], [ -86.74318, 33.49743 ], [ -86.74342, 33.49866 ], [ -86.74413, 33.49878 ], [ -86.74508, 33.49922 ], [ -86.7458, 33.50016 ], [ -86.7459, 33.49704 ], [ -86.74605, 33.49662 ], [ -86.75207, 33.4921 ], [ -86.75218, 33.49175 ], [ -86.75225, 33.49044 ], [ -86.75225, 33.48779 ], [ -86.75289, 33.4869 ], [ -86.75396, 33.48764 ], [ -86.7545, 33.48883 ], [ -86.76227, 33.48892 ], [ -86.76202, 33.49192 ], [ -86.75692, 33.49579 ], [ -86.75678, 33.49754 ] ] ] ] } }, -{ "type": "Feature", "properties": { "area_id": 193, "city": "Birmingham", "state": "AL", "city_survey": true, "category": "Best", "grade": "A", "label": "A2", "residential": true, "commercial": false, "industrial": false, "fill": "#76a865" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -86.75867, 33.50933 ], [ -86.76134, 33.5124 ], [ -86.76188, 33.51196 ], [ -86.76333, 33.51183 ], [ -86.76408, 33.51156 ], [ -86.76452, 33.51101 ], [ -86.76488, 33.51027 ], [ -86.76619, 33.50985 ], [ -86.76699, 33.50963 ], [ -86.76809, 33.50888 ], [ -86.76844, 33.50841 ], [ -86.76916, 33.50787 ], [ -86.77052, 33.50742 ], [ -86.77186, 33.50653 ], [ -86.77314, 33.50574 ], [ -86.77373, 33.50532 ], [ -86.77543, 33.50311 ], [ -86.77596, 33.50276 ], [ -86.77988, 33.50078 ], [ -86.78059, 33.50103 ], [ -86.78125, 33.50093 ], [ -86.78196, 33.50071 ], [ -86.78259, 33.50034 ], [ -86.78297, 33.49969 ], [ -86.78276, 33.49833 ], [ -86.78377, 33.49823 ], [ -86.78478, 33.49786 ], [ -86.78556, 33.49739 ], [ -86.78565, 33.49692 ], [ -86.79055, 33.49427 ], [ -86.79278, 33.49474 ], [ -86.79413, 33.49377 ], [ -86.79458, 33.49308 ], [ -86.79455, 33.49253 ], [ -86.79392, 33.4904 ], [ -86.78137, 33.49603 ], [ -86.78122, 33.4937 ], [ -86.77843, 33.49367 ], [ -86.77846, 33.49392 ], [ -86.77421, 33.4938 ], [ -86.7743, 33.49538 ], [ -86.77335, 33.49543 ], [ -86.7713, 33.49679 ], [ -86.76975, 33.49707 ], [ -86.76916, 33.49754 ], [ -86.76841, 33.49786 ], [ -86.76723, 33.49776 ], [ -86.76624, 33.49786 ], [ -86.76556, 33.49826 ], [ -86.76518, 33.49883 ], [ -86.76387, 33.50026 ], [ -86.76339, 33.50076 ], [ -86.7625, 33.50115 ], [ -86.76119, 33.50162 ], [ -86.75947, 33.50371 ], [ -86.75804, 33.50326 ], [ -86.75742, 33.50338 ], [ -86.75689, 33.50363 ], [ -86.76093, 33.50807 ], [ -86.75867, 33.50933 ] ] ] ] } }, -{ "type": "Feature", "properties": { "area_id": 206, "city": "Birmingham", "state": "AL", "city_survey": true, "category": "Best", "grade": "A", "label": "A3", "residential": true, "commercial": false, "industrial": false, "fill": "#76a865" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -86.75678, 33.49754 ], [ -86.75692, 33.49579 ], [ -86.76202, 33.49192 ], [ -86.76227, 33.48892 ], [ -86.7545, 33.48883 ], [ -86.75396, 33.48764 ], [ -86.75289, 33.4869 ], [ -86.75225, 33.48779 ], [ -86.75225, 33.49044 ], [ -86.75218, 33.49175 ], [ -86.75207, 33.4921 ], [ -86.74605, 33.49662 ], [ -86.7459, 33.49704 ], [ -86.7458, 33.50016 ], [ -86.74605, 33.5009 ], [ -86.75196, 33.50135 ], [ -86.75678, 33.49754 ] ] ] ] } }, -{ "type": "Feature", "properties": { "area_id": 203, "city": "Birmingham", "state": "AL", "city_survey": true, "category": "Still Desirable", "grade": "B", "label": "B1", "residential": true, "commercial": false, "industrial": false, "fill": "#7cb5bd" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -86.80111, 33.48071 ], [ -86.80505, 33.48044 ], [ -86.8069, 33.47782 ], [ -86.81346, 33.4777 ], [ -86.81289, 33.47137 ], [ -86.81546, 33.46973 ], [ -86.80929, 33.46479 ], [ -86.80491, 33.46503 ], [ -86.79564, 33.46801 ], [ -86.79503, 33.46765 ], [ -86.79179, 33.46815 ], [ -86.791, 33.46857 ], [ -86.79068, 33.46946 ], [ -86.79004, 33.46997 ], [ -86.78884, 33.47047 ], [ -86.78784, 33.4714 ], [ -86.78781, 33.47169 ], [ -86.78809, 33.47193 ], [ -86.78898, 33.47205 ], [ -86.78684, 33.47342 ], [ -86.78563, 33.4736 ], [ -86.7847, 33.47351 ], [ -86.78367, 33.47312 ], [ -86.78246, 33.47244 ], [ -86.78182, 33.47125 ], [ -86.78189, 33.47059 ], [ -86.78296, 33.4697 ], [ -86.78303, 33.46911 ], [ -86.78157, 33.46679 ], [ -86.77237, 33.47039 ], [ -86.77119, 33.47149 ], [ -86.76937, 33.47235 ], [ -86.76706, 33.4736 ], [ -86.76956, 33.47449 ], [ -86.7697, 33.4758 ], [ -86.77819, 33.47621 ], [ -86.77822, 33.47978 ], [ -86.77651, 33.47969 ], [ -86.77651, 33.48314 ], [ -86.77986, 33.48151 ], [ -86.78001, 33.48368 ], [ -86.7829, 33.48354 ], [ -86.78409, 33.48361 ], [ -86.78489, 33.48282 ], [ -86.78706, 33.48213 ], [ -86.78694, 33.47876 ], [ -86.78697, 33.47635 ], [ -86.79232, 33.4764 ], [ -86.79342, 33.47506 ], [ -86.80049, 33.47464 ], [ -86.80067, 33.47662 ], [ -86.79874, 33.47722 ], [ -86.79811, 33.4789 ], [ -86.80058, 33.4795 ], [ -86.80099, 33.48024 ], [ -86.80111, 33.48071 ] ] ] ] } }, -{ "type": "Feature", "properties": { "area_id": 189, "city": "Birmingham", "state": "AL", "city_survey": true, "category": "Still Desirable", "grade": "B", "label": "B10", "residential": true, "commercial": false, "industrial": false, "fill": "#7cb5bd" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -86.74923, 33.53333 ], [ -86.74971, 33.5333 ], [ -86.7532, 33.53148 ], [ -86.75446, 33.53233 ], [ -86.75539, 33.53201 ], [ -86.75605, 33.53123 ], [ -86.75473, 33.53029 ], [ -86.75473, 33.52977 ], [ -86.75437, 33.52938 ], [ -86.75389, 33.52908 ], [ -86.74916, 33.53081 ], [ -86.74923, 33.53333 ] ] ] ] } } +{ "type": "Feature", "properties": { "area_id": 3569, "city": "New Haven", "state": "CT", "city_survey": true, "category": "Best", "grade": "A", "label": "A1", "residential": true, "commercial": false, "industrial": false, "fill": "#76a865" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -72.9, 41.36885 ], [ -72.90325, 41.36999 ], [ -72.90644, 41.37108 ], [ -72.90804, 41.36644 ], [ -72.90102, 41.36493 ], [ -72.89904, 41.36851 ], [ -72.9, 41.36885 ] ] ] ] } }, +{ "type": "Feature", "properties": { "area_id": 3568, "city": "New Haven", "state": "CT", "city_survey": true, "category": "Best", "grade": "A", "label": "A2", "residential": true, "commercial": false, "industrial": false, "fill": "#76a865" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -72.89401, 41.36331 ], [ -72.89256, 41.36574 ], [ -72.89904, 41.36851 ], [ -72.90102, 41.36493 ], [ -72.89401, 41.36331 ] ] ] ] } }, +{ "type": "Feature", "properties": { "area_id": 3566, "city": "New Haven", "state": "CT", "city_survey": true, "category": "Best", "grade": "A", "label": "A3", "residential": true, "commercial": false, "industrial": false, "fill": "#76a865" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -72.90981, 41.35949 ], [ -72.90284, 41.35795 ], [ -72.90178, 41.36076 ], [ -72.90132, 41.36172 ], [ -72.90069, 41.36266 ], [ -72.90062, 41.36305 ], [ -72.90078, 41.36333 ], [ -72.90073, 41.36396 ], [ -72.90102, 41.36493 ], [ -72.90804, 41.36644 ], [ -72.90644, 41.37108 ], [ -72.90768, 41.37158 ], [ -72.90794, 41.37081 ], [ -72.90844, 41.37044 ], [ -72.90842, 41.36985 ], [ -72.90868, 41.36905 ], [ -72.90935, 41.3684 ], [ -72.91134, 41.36756 ], [ -72.9125, 41.36733 ], [ -72.91544, 41.36673 ], [ -72.91561, 41.36632 ], [ -72.91753, 41.36557 ], [ -72.91818, 41.36519 ], [ -72.91832, 41.36462 ], [ -72.91839, 41.3642 ], [ -72.91383, 41.36306 ], [ -72.91114, 41.36279 ], [ -72.90932, 41.36237 ], [ -72.9096, 41.35969 ], [ -72.90981, 41.35949 ] ] ] ] } }, +{ "type": "Feature", "properties": { "area_id": 3567, "city": "New Haven", "state": "CT", "city_survey": true, "category": "Best", "grade": "A", "label": "A4", "residential": true, "commercial": false, "industrial": false, "fill": "#76a865" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -72.90284, 41.35795 ], [ -72.8999, 41.35712 ], [ -72.89788, 41.36147 ], [ -72.89564, 41.36069 ], [ -72.89401, 41.36331 ], [ -72.90102, 41.36493 ], [ -72.90073, 41.36396 ], [ -72.90078, 41.36333 ], [ -72.90062, 41.36305 ], [ -72.90069, 41.36266 ], [ -72.90132, 41.36172 ], [ -72.90178, 41.36076 ], [ -72.90284, 41.35795 ] ] ] ] } }, +{ "type": "Feature", "properties": { "area_id": 3564, "city": "New Haven", "state": "CT", "city_survey": true, "category": "Best", "grade": "A", "label": "A5", "residential": true, "commercial": false, "industrial": false, "fill": "#76a865" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -72.89801, 41.3493 ], [ -72.89883, 41.34937 ], [ -72.9027, 41.35013 ], [ -72.90569, 41.34575 ], [ -72.90976, 41.34024 ], [ -72.90956, 41.33932 ], [ -72.90925, 41.33868 ], [ -72.90892, 41.33903 ], [ -72.90857, 41.33906 ], [ -72.90721, 41.33878 ], [ -72.90567, 41.34024 ], [ -72.90249, 41.33966 ], [ -72.90192, 41.33845 ], [ -72.89924, 41.34017 ], [ -72.89837, 41.34152 ], [ -72.90214, 41.34213 ], [ -72.90237, 41.3438 ], [ -72.90121, 41.34359 ], [ -72.90016, 41.34354 ], [ -72.89875, 41.34364 ], [ -72.89792, 41.34374 ], [ -72.89748, 41.34626 ], [ -72.89735, 41.34745 ], [ -72.89731, 41.34854 ], [ -72.89758, 41.34911 ], [ -72.89801, 41.3493 ] ] ] ] } } ] } diff --git a/examples/espm-157/spatial-1.ipynb b/examples/espm-157/spatial-1.ipynb index 2539af8b9..1a762a55f 100644 --- a/examples/espm-157/spatial-1.ipynb +++ b/examples/espm-157/spatial-1.ipynb @@ -7,7 +7,13 @@ "source": [ "# ESPM 157 - Intro to Spatial Data\n", "\n", - "" + "\n", + "\n", + "Install dependencies:\n", + "\n", + "```bash\n", + "micromamba install geopandas ibis-duckdb\n", + "```" ] }, { @@ -31,10 +37,7 @@ "metadata": {}, "outputs": [], "source": [ - "# IOException: IO Error: GDAL Error (11): CURL error: error setting certificate file: /etc/pki/tls/certs/ca-bundle.crt\n", - "# redlines = con.read_geo(\"/vsicurl/https://dsl.richmond.edu/panorama/redlining/static/mappinginequality.gpkg\")\n", - "\n", - "redlines = con.read_geo(\"./mappinginequality.gpkg\")" + "redlines = con.read_geo(\"/vsicurl/https://dsl.richmond.edu/panorama/redlining/static/mappinginequality.gpkg\")" ] }, { @@ -71,43 +74,43 @@ " \n", " \n", " 0\n", - " Montgomery\n", + " Denver\n", " \n", " \n", " 1\n", - " Fresno\n", + " Miami\n", " \n", " \n", " 2\n", - " Colorado Springs\n", + " St. Petersburg\n", " \n", " \n", " 3\n", - " DeLand\n", + " Waterloo\n", " \n", " \n", " 4\n", - " Orlando\n", + " Peoria\n", " \n", " \n", " 5\n", - " Grand Rapids\n", + " South Bend\n", " \n", " \n", " 6\n", - " Fargo\n", + " Junction City\n", " \n", " \n", " 7\n", - " Atlantic City\n", + " Belmont\n", " \n", " \n", " 8\n", - " Atchison\n", + " Fall River\n", " \n", " \n", " 9\n", - " Brookline\n", + " Holyoke Chicopee\n", " \n", " \n", "\n", @@ -115,16 +118,16 @@ ], "text/plain": [ " city\n", - "0 Montgomery\n", - "1 Fresno\n", - "2 Colorado Springs\n", - "3 DeLand\n", - "4 Orlando\n", - "5 Grand Rapids\n", - "6 Fargo\n", - "7 Atlantic City\n", - "8 Atchison\n", - "9 Brookline" + "0 Denver\n", + "1 Miami\n", + "2 St. Petersburg\n", + "3 Waterloo\n", + "4 Peoria\n", + "5 South Bend\n", + "6 Junction City\n", + "7 Belmont\n", + "8 Fall River\n", + "9 Holyoke Chicopee" ] }, "execution_count": 3, @@ -133,6 +136,7 @@ } ], "source": [ + "# First 10 distinct cities\n", "redlines.select(redlines.city).distinct().head(10).execute()" ] }, @@ -144,14 +148,14 @@ "metadata": {}, "outputs": [], "source": [ - "# city = redlines.filter(_.city == \"Birmingham\")" + "city = redlines.filter(redlines.city == \"New Haven\")" ] }, { "cell_type": "code", "execution_count": 5, "execution_state": "idle", - "id": "c6b07243-9e7f-4b07-94b3-52e78019a59e", + "id": "34f7f4d6-28d6-4716-8e03-ac32c6ae3bb7", "metadata": {}, "outputs": [ { @@ -166,7 +170,7 @@ }, { "data": { - "image/png": "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4Wapqak4fvw4ZsyY0XRfZGQkvLy88Mknn6C+vh5Xr17FJ598gp49e6Jbt27GfqkWTcz3wRjtWgsx3wcAOH36NP72t79h9erVt9z4jzpI7NRE0paXlyfEx8cLMplMUCgUQkBAQLMunAULFgj29vZCZGSksGnTJmH//v3CyJEjhcjISEGr1bbZbnl5uZCYmCgAEOzs7ASVSiWkpKQ0HX/nnXeEHj16tHiej4+PsHTpUuO+SAkw1ftws9mzZws9e/ZscX96eroQFhYmyOVyQS6XC5GRkUJOTo7RXpuUiPU+GKNdayLW+1BXVyfExsYKn3/+uSAIgrB9+3ZeaTEyhhZq4ZVXXhEA3PJ25swZQa/XCxMmTBDGjh0r7NmzR0hNTRVmz54tdOnSRSgsLBQE4Vq4ANAscJSUlAhyuVzYtGlTmzU899xzwoABA4QtW7YIx48fF+bNmyeo1WohLS2tqV1rDy2W8D7cUFtbK6jVamHx4sUt7h8wYIAwbdo04dChQ8L+/fuF++67T4iJiRFqa2uN+xciEim8D51tVwqk8D78+c9/Fh566KGm7xlajI+hhVooKSkRzpw5c8ubVqsVtmzZIsjlckGj0TR7fnh4uLBgwQJBEATh008/FQAIeXl5zR7j6+srfPzxx62ePysrSwAgnDx5stn9I0eOFGbOnCkIgiB88skngru7e7PjDQ0NgkKhEL777rtOvX5LIfb7cLPVq1cL9vb2QklJSbP7V6xYIfj6+go6na7pPq1WK7i4uAhfffVVR1+6RZHC+9DZdqVACu9DXFycIJfLBYVCISgUCkEulwsABIVCIbzxxhud/BsgQRAEOxP0OJHE+fj4wMfH57aPq62tBYAW/bZyuRx6vR4AMGTIEABAZmYmgoKCAFybqnzlyhV07drVoHYVCkVTuwkJCaioqEBqampTP/W2bdug1+sxcODAdr1OSyf2+3CzTz75BBMmTGhRT21tLeRyOWQyWbPzymSypnNLnRTeh862KwVSeB++/fZbXL16ten7w4cP44knnsDu3bsRFhZ223apHcROTSRdly9fFry8vIR7771XOH78uJCZmSm8+OKLgr29vXD8+PGmxyUnJwsxMTHC3r17hfT0dOGee+4RoqOjm6bE5ufnC5GRkcLBgwcFQRCE+vp6ITw8XLjzzjuFgwcPCllZWcLixYsFmUwm/Pzzz03tJiUlCX369BEOHjwo7NmzR4iIiLDJKc+meh9uOHfunCCTyYSNGze2OPeZM2cER0dHYfbs2cLp06eFkydPCo8++qigVqubLsXbCjHfh/a0ayvEfh9uxu4h42NooU45fPiwkJiYKHh6egpubm7CoEGDhF9++aXZYzQajfDEE08I7u7ugqenpzBp0qRmU2QvXLggABC2b9/edN/Zs2eFe++9V/D19RVcXFyE2NjYFlOgS0tLhcmTJwtKpVJQqVTC448/3up6CbbAVO+DIAjC3LlzheDg4GZdQDf79ddfhSFDhghqtVrw8PAQRowYIezfv9/or1EKxHwfbteuLRHzfbgZQ4vxyQRBEMS80kNERETUHpxETkRERJLA0EJERESSwNBCREREksDQQkRERJLA0EJERESSwNBCREREksDQQkRERJLA0EJERESSwNBCREREksDQQkRERJLA0EJERESSwNBCREREkvD/K3G3EXBzbMkAAAAASUVORK5CYII=", + "image/png": "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goODMWnSJHz77beIjIwE0DWmu6CgADt37kRTUxNCQ0Mxc+ZMvP/++1CpVO6+TEII4YyIMca4LoIvjEYj1Go1DAYD/Pz8uC6HEDLE9DWDaK4SQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRmH4Fd1ZWFkQiEZ555hn7tuzsbKSlpSEoKAgikQj5+fl3PE52djZSUlLg7+8PpVKJ5ORk7Nq1y2GfzMxMiEQihw+tVuuwD2MMmZmZCAsLg1wuR2pqKs6fP9+fSySEEN5xObhzcnKwdetWJCUlOWw3m82YOnUqXnnllT4fKyAgAOvXr8fJkydx9uxZpKenIz09Hfv373fY7+6770Z1dbX9o6CgwOH91157DW+88QY2b96MnJwcaLVazJkzB83Nza5eJiGE8I6XK59kMpmwZMkSbNu2DRs3bnR4b+nSpQCAkpKSPh8vNTXV4fWaNWuwY8cOHD9+HGlpaTeL9fLq1sq+gTGGN998E+vXr8eCBQsAADt27EBISAjeffddrFy5ss/1EEIIn7nU4l69ejXmzp2L2bNnu7seMMZw+PBhFBUVYfr06Q7vXb58GWFhYYiOjsbixYtx7do1+3vFxcXQ6XS4//777dtkMhlmzJiBEydO9Hgui8UCo9Ho8EGIkPzuwzNI+/MxZHyQj+3fFCOnpAEt1g6uyyIDzOkW9549e5CXl4ecnBy3FmIwGBAeHg6LxQKJRIK3334bc+bMsb8/ceJE7Ny5EyNHjkRNTQ02btyIKVOm4Pz58wgMDIROpwMAhISEOBw3JCQEpaWlPZ4zKysLL774oluvg5DB8sU5HT7MrQAAFNU0IzuvEgAgFgHRQUokhquRGKZGYrgad4f7wc/Hm8tyiRs5Fdzl5eVYs2YNDhw4AB8fH7cWolKpkJ+fD5PJhMOHDyMj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", "text/plain": [ "
" ] @@ -176,22 +180,10 @@ } ], "source": [ - "city_gdf = redlines.head().execute()\n", + "city_gdf = city.head().execute()\n", "city_gdf.plot()" ] }, - { - "cell_type": "code", - "execution_count": 6, - "execution_state": "idle", - "id": "34f7f4d6-28d6-4716-8e03-ac32c6ae3bb7", - "metadata": {}, - "outputs": [], - "source": [ - "# city_gdf = city.head().execute()\n", - "# city_gdf.plot()" - ] - }, { "cell_type": "markdown", "id": "d37a610a-1444-43a3-900f-dfe16a890ab9", @@ -204,52 +196,27 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "execution_state": "idle", "id": "94875994-926a-46b4-be29-be7c676a9905", "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.ywidget-view+json": { - "model_id": "573317eb5d694bb78052fab671454827", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "from jupytergis_lab import GISDocument\n", + "# from jupytergis_lab import GISDocument\n", "\n", - "doc = GISDocument(\"./debug.jgis\")\n", - "doc" + "# doc = GISDocument(\"./debug.jGIS\")\n", + "# doc" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "execution_state": "idle", "id": "642e9281-d9d5-4f35-a7f6-65bf1096cb1f", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/mfisher/.local/share/micromamba/envs/jupytergis-dev/lib/python3.12/site-packages/pyogrio/geopandas.py:662: UserWarning: 'crs' was not provided. The output dataset will not have projection information defined and may not be usable in other systems.\n", - " write(\n" - ] - } - ], + "outputs": [], "source": [ - "city_gdf.to_file(\"new_haven.json\")" + "# city_gdf.to_file(\"new_haven.json\")" ] }, { @@ -260,22 +227,25 @@ "metadata": {}, "outputs": [ { - "ename": "ModuleNotFoundError", - "evalue": "No module named 'jupytergis'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[9], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mjupytergis\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mnotebook\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mgeo_debug\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m geo_debug\n\u001b[1;32m 4\u001b[0m geo_debug(city_gdf)\n", - "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'jupytergis'" - ] + "data": { + "application/vnd.jupyter.ywidget-view+json": { + "model_id": "17493598bcbe4db1b80e0b5f0273bf3d", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ - "from jupytergis.notebook.geo_debug import geo_debug\n", + "from jupytergis_lab.notebook.geo_debug import geo_debug\n", "\n", "\n", - "geo_debug(city_gdf)" + "geo_debug(\"new_haven.json\")" ] } ], diff --git a/examples/espm-157/spatial-1.jGIS b/examples/espm-157/spatial-1.jGIS index 55f0130e1..a77ade24d 100644 --- a/examples/espm-157/spatial-1.jGIS +++ b/examples/espm-157/spatial-1.jGIS @@ -36,10 +36,10 @@ "options": { "bearing": 0.0, "extent": [ - -8451224.227063052, - 3855330.008457263, - -7419889.364430341, - 5816290.181194804 + -8967908.753904685, + 4200125.391526781, + -6903204.837588707, + 5471494.798125289 ], "latitude": 39.79233083947287, "longitude": -71.28631957617792, @@ -80,4 +80,4 @@ "type": "RasterSource" } } -} +} \ No newline at end of file From 9f3b6b8483cd387894b56ea04f5c00a2155e58d1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 Jan 2025 02:04:00 +0000 Subject: [PATCH 13/46] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/espm-157/debug.jGIS | 2 +- examples/espm-157/spatial-1.jGIS | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/espm-157/debug.jGIS b/examples/espm-157/debug.jGIS index c5891b8ee..5f747defb 100644 --- a/examples/espm-157/debug.jGIS +++ b/examples/espm-157/debug.jGIS @@ -1039,4 +1039,4 @@ "type": "RasterSource" } } -} \ No newline at end of file +} diff --git a/examples/espm-157/spatial-1.jGIS b/examples/espm-157/spatial-1.jGIS index a77ade24d..fa6818ffc 100644 --- a/examples/espm-157/spatial-1.jGIS +++ b/examples/espm-157/spatial-1.jGIS @@ -80,4 +80,4 @@ "type": "RasterSource" } } -} \ No newline at end of file +} From ea3f08eb6e673cb7febe78ac956463c6984e4647 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Tue, 25 Mar 2025 17:10:25 -0600 Subject: [PATCH 14/46] Open JGIS in sidecar --- python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py b/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py index cbebee2e0..4ff650736 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py @@ -1,4 +1,4 @@ -# from jupyterlab import +from sidecar import Sidecar from jupytergis_lab import GISDocument @@ -27,4 +27,7 @@ def geo_debug(geojson_path: str) -> None: # TODO: Activate left and right panel -- not sure how feasible yet from Python. # Also, if using sidecar, right panel can't be displayed. Can we open a # "native" JupyterLab pane instead of using sidecar? - display(doc) + + sc = Sidecar(title="JupyterGIS sidecar") + with sc: + display(doc) From 71b55408c237f1cd06617629d8048394a6e38558 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Thu, 27 Mar 2025 11:26:52 -0600 Subject: [PATCH 15/46] Create an anonymous project --- python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py b/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py index 4ff650736..e6266439a 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py @@ -5,9 +5,7 @@ def geo_debug(geojson_path: str) -> None: """Run a JupyterGIS data interaction interface alongside a Notebook.""" - # TODO: allow user to specify a different project file; - # TODO: Just create the .jgis file - doc = GISDocument("debug.jGIS") + doc = GISDocument() # TODO: Basemap choices doc.add_raster_layer( From 6faedf49cd992aa33207cb60e669f55e272102c5 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Thu, 27 Mar 2025 11:27:15 -0600 Subject: [PATCH 16/46] Open in a new JupyterLab window/tab! --- python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py b/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py index e6266439a..5e07fce29 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py @@ -26,6 +26,6 @@ def geo_debug(geojson_path: str) -> None: # Also, if using sidecar, right panel can't be displayed. Can we open a # "native" JupyterLab pane instead of using sidecar? - sc = Sidecar(title="JupyterGIS sidecar") + sc = Sidecar(title="JupyterGIS sidecar", anchor="split-right") with sc: display(doc) From d26280a201f99062d2274796b4c69b480c859675 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Thu, 27 Mar 2025 11:28:05 -0600 Subject: [PATCH 17/46] Update TODO comments --- python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py b/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py index 5e07fce29..49eb9264b 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py @@ -12,16 +12,13 @@ def geo_debug(geojson_path: str) -> None: "https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}" ) - # TODO: Support lots of file types + # TODO: Support lots of file types, and support Python objects like geodataframes. doc.add_geojson_layer(geojson_path) # TODO: Zoom to layer; is that feasible to do from Python? Currently not exposed in # Python API. # TODO: Make map take up the whole sidebar space. - # TODO: Toolbar not visible. - # /home/shared/jupytergis/packages/base/src/toolbar/widget.tsx - # # TODO: Activate left and right panel -- not sure how feasible yet from Python. # Also, if using sidecar, right panel can't be displayed. Can we open a # "native" JupyterLab pane instead of using sidecar? From 27285fde1a30fc6d11b9d7d52ea7627050bd467d Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Mon, 14 Apr 2025 21:23:56 -0600 Subject: [PATCH 18/46] Rename geo_debug -> explore Also move it up a level in the package --- .../jupytergis_lab/{notebook/geo_debug.py => explore.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename python/jupytergis_lab/jupytergis_lab/{notebook/geo_debug.py => explore.py} (95%) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py b/python/jupytergis_lab/jupytergis_lab/explore.py similarity index 95% rename from python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py rename to python/jupytergis_lab/jupytergis_lab/explore.py index 49eb9264b..8ff8cc281 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/geo_debug.py +++ b/python/jupytergis_lab/jupytergis_lab/explore.py @@ -3,7 +3,7 @@ from jupytergis_lab import GISDocument -def geo_debug(geojson_path: str) -> None: +def explore(geojson_path: str) -> None: """Run a JupyterGIS data interaction interface alongside a Notebook.""" doc = GISDocument() From 38165085a8377389d67c4e9ea0e9e35ccae7834b Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Mon, 14 Apr 2025 22:09:06 -0600 Subject: [PATCH 19/46] Explore explore function at jupytergis package --- python/jupytergis/jupytergis/__init__.py | 2 +- python/jupytergis_lab/jupytergis_lab/__init__.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/python/jupytergis/jupytergis/__init__.py b/python/jupytergis/jupytergis/__init__.py index 3097075a4..139b9e470 100644 --- a/python/jupytergis/jupytergis/__init__.py +++ b/python/jupytergis/jupytergis/__init__.py @@ -1,3 +1,3 @@ __version__ = "0.4.4" -from jupytergis_lab import GISDocument # noqa +from jupytergis_lab import GISDocument, explore # noqa diff --git a/python/jupytergis_lab/jupytergis_lab/__init__.py b/python/jupytergis_lab/jupytergis_lab/__init__.py index 0d4f3c58f..1f9d6d96b 100644 --- a/python/jupytergis_lab/jupytergis_lab/__init__.py +++ b/python/jupytergis_lab/jupytergis_lab/__init__.py @@ -9,6 +9,7 @@ __version__ = "dev" from .notebook import GISDocument # noqa +from .explore import explore def _jupyter_labextension_paths(): From cb82c6f987c448822707d1ea8b768ea210b7d5c8 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Wed, 16 Apr 2025 07:29:11 -0600 Subject: [PATCH 20/46] Update docstring and parameter type --- python/jupytergis_lab/jupytergis_lab/explore.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/explore.py b/python/jupytergis_lab/jupytergis_lab/explore.py index 8ff8cc281..2f8f7a512 100644 --- a/python/jupytergis_lab/jupytergis_lab/explore.py +++ b/python/jupytergis_lab/jupytergis_lab/explore.py @@ -1,10 +1,15 @@ +from pathlib import Path + from sidecar import Sidecar from jupytergis_lab import GISDocument -def explore(geojson_path: str) -> None: - """Run a JupyterGIS data interaction interface alongside a Notebook.""" +def explore(geojson_path: str | Path) -> None: + """Run a JupyterGIS data interaction interface alongside a Notebook. + + :param geojson_path: Path to a GeoJSON file. + """ doc = GISDocument() # TODO: Basemap choices @@ -13,7 +18,7 @@ def explore(geojson_path: str) -> None: ) # TODO: Support lots of file types, and support Python objects like geodataframes. - doc.add_geojson_layer(geojson_path) + doc.add_geojson_layer(str(geojson_path)) # TODO: Zoom to layer; is that feasible to do from Python? Currently not exposed in # Python API. From 41662ae3cfddecc47765f89360ca67500ca11eb8 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Wed, 16 Apr 2025 07:30:12 -0600 Subject: [PATCH 21/46] Open JGIS Document as a full window, not widget --- python/jupytergis_lab/jupytergis_lab/explore.py | 13 +++---------- .../jupytergis_lab/notebook/gis_document.py | 16 ++++++++++------ python/jupytergis_lab/src/notebookrenderer.ts | 13 ++++++++++++- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/explore.py b/python/jupytergis_lab/jupytergis_lab/explore.py index 2f8f7a512..5dffa498a 100644 --- a/python/jupytergis_lab/jupytergis_lab/explore.py +++ b/python/jupytergis_lab/jupytergis_lab/explore.py @@ -10,7 +10,7 @@ def explore(geojson_path: str | Path) -> None: :param geojson_path: Path to a GeoJSON file. """ - doc = GISDocument() + doc = GISDocument(open=True) # TODO: Basemap choices doc.add_raster_layer( @@ -22,12 +22,5 @@ def explore(geojson_path: str | Path) -> None: # TODO: Zoom to layer; is that feasible to do from Python? Currently not exposed in # Python API. - - # TODO: Make map take up the whole sidebar space. - # TODO: Activate left and right panel -- not sure how feasible yet from Python. - # Also, if using sidecar, right panel can't be displayed. Can we open a - # "native" JupyterLab pane instead of using sidecar? - - sc = Sidecar(title="JupyterGIS sidecar", anchor="split-right") - with sc: - display(doc) + # FIXME: The document opens as intended, but the file has no contents and any + # updates performed result in no update to the file. diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py index a5fa558b9..ed2f45383 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py @@ -49,6 +49,7 @@ class GISDocument(CommWidget): def __init__( self, path: Optional[str | Path] = None, + open: bool = False, latitude: Optional[float] = None, longitude: Optional[float] = None, zoom: Optional[float] = None, @@ -65,7 +66,7 @@ def __init__( super().__init__( comm_metadata={ "ymodel_name": "@jupytergis:widget", - **self._path_to_comm(path), + **self._make_comm(path=path, open=open), }, ydoc=ydoc, ) @@ -730,13 +731,11 @@ def _add_layer(self, new_object: "JGISObject"): return _id @classmethod - def _path_to_comm(cls, filePath: Optional[str]) -> Dict: - path = None + def _make_comm(cls, *, path: Optional[str], open: bool = False) -> Dict: format = None contentType = None - if filePath is not None: - path = filePath + if path is not None: file_name = Path(path).name try: ext = file_name.split(".")[1].lower() @@ -754,8 +753,13 @@ def _path_to_comm(cls, filePath: Optional[str]) -> Dict: contentType = "QGS" else: raise ValueError("File extension is not supported!") + return dict( - path=path, format=format, contentType=contentType, create_ydoc=path is None + path=path, + open=open, + format=format, + contentType=contentType, + create_ydoc=path is None, ) def to_py(self) -> dict: diff --git a/python/jupytergis_lab/src/notebookrenderer.ts b/python/jupytergis_lab/src/notebookrenderer.ts index caf0fa19a..51a50fade 100644 --- a/python/jupytergis_lab/src/notebookrenderer.ts +++ b/python/jupytergis_lab/src/notebookrenderer.ts @@ -134,7 +134,7 @@ export const notebookRendererPlugin: JupyterFrontEndPlugin = { protected async initialize(commMetadata: { [key: string]: any; }): Promise { - const { path, format, contentType } = commMetadata; + const { path, open, format, contentType } = commMetadata; const fileFormat = format as Contents.FileFormat; if (!drive) { @@ -195,6 +195,17 @@ export const notebookRendererPlugin: JupyterFrontEndPlugin = { this.ydoc = this.jupyterGISModel.sharedModel.ydoc; this.sharedModel = new JupyterYDoc(commMetadata, this.ydoc); + + if (open) { + app.commands.execute('docmanager:open', { + path: localPath, + factory: 'JupyterGIS .jgis Viewer', + options: { + mode: 'split-right', + } + }); + } + } } From 3fdee4f66015c21312dc71f63d8e7a4773d41e36 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Wed, 16 Apr 2025 09:12:11 -0600 Subject: [PATCH 22/46] Hack around document opening but not displaying layers as expected Co-authored-by: Greg Mooney Co-authored-by: Kristin Davis Co-authored-by: Sanjay Bhangar Co-authored-by: Aman Ahuja Co-authored-by: Jonathan Marokhovsky Co-authored-by: Tammy Woodard --- python/jupytergis_lab/src/notebookrenderer.ts | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/python/jupytergis_lab/src/notebookrenderer.ts b/python/jupytergis_lab/src/notebookrenderer.ts index 51a50fade..ea6cf689b 100644 --- a/python/jupytergis_lab/src/notebookrenderer.ts +++ b/python/jupytergis_lab/src/notebookrenderer.ts @@ -171,13 +171,24 @@ export const notebookRendererPlugin: JupyterFrontEndPlugin = { }); } } else { + // If the user did not provide a path, do not create + localPath = PathExt.join( + PathExt.dirname(currentWidgetPath), + 'unsaved_project.jGIS' + ); + // If the user did not provide a path, create an untitled document - const model = await app.serviceManager.contents.newUntitled({ - path: PathExt.dirname(currentWidgetPath), - type: 'file', - ext: '.jGIS' + // const model = await app.serviceManager.contents.newUntitled({ + // path: PathExt.dirname(currentWidgetPath), + // type: 'file', + // ext: '.jGIS' + // }); + // localPath = model.path; + + await app.serviceManager.contents.save(localPath, { + content: btoa('{}'), + format: 'base64' }); - localPath = model.path; } const sharedModel = drive!.sharedModelFactory.createNew({ @@ -197,13 +208,22 @@ export const notebookRendererPlugin: JupyterFrontEndPlugin = { this.sharedModel = new JupyterYDoc(commMetadata, this.ydoc); if (open) { - app.commands.execute('docmanager:open', { - path: localPath, - factory: 'JupyterGIS .jgis Viewer', - options: { - mode: 'split-right', - } - }); + // HACK: Use setTimeout; without it, the document is opened but + // doesn't display the layers. + // Is there some sort of race condition between document save and + // open? + // awaiting the open command does not work either. + // TODO: Remove setTimeout! + await setTimeout(() => { + app.commands.execute('docmanager:open', { + path: localPath, + factory: 'JupyterGIS .jgis Viewer', + options: { + mode: 'split-right', + } + })}, + 0 + ); } } From c00844ed1fde959848c6eceb02e9f7d8d8b2f600 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Thu, 17 Apr 2025 13:57:16 -0600 Subject: [PATCH 23/46] Switch back to sidecar approach, use CSS to handle filling space --- .../jupytergis_lab/jupytergis_lab/explore.py | 8 +++- .../jupytergis_lab/notebook/gis_document.py | 8 ++-- python/jupytergis_lab/src/notebookrenderer.ts | 37 +------------------ 3 files changed, 12 insertions(+), 41 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/explore.py b/python/jupytergis_lab/jupytergis_lab/explore.py index 5dffa498a..6a0f691ab 100644 --- a/python/jupytergis_lab/jupytergis_lab/explore.py +++ b/python/jupytergis_lab/jupytergis_lab/explore.py @@ -10,7 +10,7 @@ def explore(geojson_path: str | Path) -> None: :param geojson_path: Path to a GeoJSON file. """ - doc = GISDocument(open=True) + doc = GISDocument() # TODO: Basemap choices doc.add_raster_layer( @@ -24,3 +24,9 @@ def explore(geojson_path: str | Path) -> None: # Python API. # FIXME: The document opens as intended, but the file has no contents and any # updates performed result in no update to the file. + sidecar = Sidecar( + title="JupyterGIS explorer", + anchor="split-right", + ) + with sidecar: + display(doc) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py index ed2f45383..8fc239db3 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py @@ -43,13 +43,12 @@ class GISDocument(CommWidget): """ Create a new GISDocument object. - :param path: the path to the file that you would like to open. If not provided, a new untitled document will be created. + :param path: the path to the file that you would like to open. If not provided, a new ephemeral widget will be created. """ def __init__( self, path: Optional[str | Path] = None, - open: bool = False, latitude: Optional[float] = None, longitude: Optional[float] = None, zoom: Optional[float] = None, @@ -66,7 +65,7 @@ def __init__( super().__init__( comm_metadata={ "ymodel_name": "@jupytergis:widget", - **self._make_comm(path=path, open=open), + **self._make_comm(path=path), }, ydoc=ydoc, ) @@ -731,7 +730,7 @@ def _add_layer(self, new_object: "JGISObject"): return _id @classmethod - def _make_comm(cls, *, path: Optional[str], open: bool = False) -> Dict: + def _make_comm(cls, *, path: Optional[str]) -> Dict: format = None contentType = None @@ -756,7 +755,6 @@ def _make_comm(cls, *, path: Optional[str], open: bool = False) -> Dict: return dict( path=path, - open=open, format=format, contentType=contentType, create_ydoc=path is None, diff --git a/python/jupytergis_lab/src/notebookrenderer.ts b/python/jupytergis_lab/src/notebookrenderer.ts index ea6cf689b..cea775ad3 100644 --- a/python/jupytergis_lab/src/notebookrenderer.ts +++ b/python/jupytergis_lab/src/notebookrenderer.ts @@ -134,7 +134,7 @@ export const notebookRendererPlugin: JupyterFrontEndPlugin = { protected async initialize(commMetadata: { [key: string]: any; }): Promise { - const { path, open, format, contentType } = commMetadata; + const { path, format, contentType } = commMetadata; const fileFormat = format as Contents.FileFormat; if (!drive) { @@ -174,21 +174,8 @@ export const notebookRendererPlugin: JupyterFrontEndPlugin = { // If the user did not provide a path, do not create localPath = PathExt.join( PathExt.dirname(currentWidgetPath), - 'unsaved_project.jGIS' + 'unsaved_project' ); - - // If the user did not provide a path, create an untitled document - // const model = await app.serviceManager.contents.newUntitled({ - // path: PathExt.dirname(currentWidgetPath), - // type: 'file', - // ext: '.jGIS' - // }); - // localPath = model.path; - - await app.serviceManager.contents.save(localPath, { - content: btoa('{}'), - format: 'base64' - }); } const sharedModel = drive!.sharedModelFactory.createNew({ @@ -206,26 +193,6 @@ export const notebookRendererPlugin: JupyterFrontEndPlugin = { this.ydoc = this.jupyterGISModel.sharedModel.ydoc; this.sharedModel = new JupyterYDoc(commMetadata, this.ydoc); - - if (open) { - // HACK: Use setTimeout; without it, the document is opened but - // doesn't display the layers. - // Is there some sort of race condition between document save and - // open? - // awaiting the open command does not work either. - // TODO: Remove setTimeout! - await setTimeout(() => { - app.commands.execute('docmanager:open', { - path: localPath, - factory: 'JupyterGIS .jgis Viewer', - options: { - mode: 'split-right', - } - })}, - 0 - ); - } - } } From eba50f11e3e289545ee4791b293132ff396d8c61 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Thu, 17 Apr 2025 14:09:08 -0600 Subject: [PATCH 24/46] Add dedicated explore example --- examples/espm-157/README.md | 1 - examples/espm-157/debug.jGIS | 1042 ----------------------------- examples/espm-157/new_haven.json | 11 - examples/espm-157/spatial-1.ipynb | 273 -------- examples/espm-157/spatial-1.jGIS | 83 --- examples/explore.ipynb | 45 ++ 6 files changed, 45 insertions(+), 1410 deletions(-) delete mode 100644 examples/espm-157/README.md delete mode 100644 examples/espm-157/debug.jGIS delete mode 100644 examples/espm-157/new_haven.json delete mode 100644 examples/espm-157/spatial-1.ipynb delete mode 100644 examples/espm-157/spatial-1.jGIS create mode 100644 examples/explore.ipynb diff --git a/examples/espm-157/README.md b/examples/espm-157/README.md deleted file mode 100644 index 487391de2..000000000 --- a/examples/espm-157/README.md +++ /dev/null @@ -1 +0,0 @@ -https://espm-157.carlboettiger.info diff --git a/examples/espm-157/debug.jGIS b/examples/espm-157/debug.jGIS deleted file mode 100644 index 5f747defb..000000000 --- a/examples/espm-157/debug.jGIS +++ /dev/null @@ -1,1042 +0,0 @@ -{ - "layerTree": [ - "72c882c3-927c-4876-b561-fc6929057571", - "4722d40c-e025-404e-a28f-a960c9e5549e" - ], - "layers": { - "4722d40c-e025-404e-a28f-a960c9e5549e": { - "filters": { - "appliedFilters": [ - { - "feature": null, - "operator": null, - "value": null - } - ], - "logicalOp": null - }, - "name": "GeoJSON Layer", - "parameters": { - "color": null, - "opacity": 1.0, - "source": "2db53792-10a5-4c0d-8c38-417877eaa4ed", - "sourceLayer": null, - "symbologyState": null, - "type": "line" - }, - "type": "VectorLayer", - "visible": true - }, - "72c882c3-927c-4876-b561-fc6929057571": { - "filters": null, - "name": "Raster Layer", - "parameters": { - "opacity": 1.0, - "source": "8f3f22ce-de8b-4a2b-8ca2-c371e717eebc" - }, - "type": "RasterLayer", - "visible": true - } - }, - "metadata": {}, - "options": { - "bearing": 0.0, - "extent": [ - -8119381.111899852, - 5062686.6590919355, - -8112203.87186153, - 5067026.019991337 - ], - "latitude": 41.35507011575021, - "longitude": -72.90540438198882, - "pitch": 0.0, - "projection": "EPSG:3857", - "zoom": 14.281457379038391 - }, - "sources": { - "0703832e-dbc3-474d-85e7-d378a637512b": { - "name": "GeoJSON Layer Source", - "parameters": { - "data": { - "bbox": null, - "features": [ - { - "bbox": null, - "geometry": { - "bbox": null, - "coordinates": [ - [ - [ - [ - -72.9, - 41.36885 - ], - [ - -72.90325, - 41.36999 - ], - [ - -72.90644, - 41.37108 - ], - [ - -72.90804, - 41.36644 - ], - [ - -72.90102, - 41.36493 - ], - [ - -72.89904, - 41.36851 - ], - [ - -72.9, - 41.36885 - ] - ] - ] - ], - "type": "MultiPolygon" - }, - "id": null, - "properties": { - "area_id": 3569.0, - "category": "Best", - "city": "New Haven", - "city_survey": true, - "commercial": false, - "fill": "#76a865", - "grade": "A", - "industrial": false, - "label": "A1", - "residential": true, - "state": "CT" - }, - "type": "Feature" - }, - { - "bbox": null, - "geometry": { - "bbox": null, - "coordinates": [ - [ - [ - [ - -72.89401, - 41.36331 - ], - [ - -72.89256, - 41.36574 - ], - [ - -72.89904, - 41.36851 - ], - [ - -72.90102, - 41.36493 - ], - [ - -72.89401, - 41.36331 - ] - ] - ] - ], - "type": "MultiPolygon" - }, - "id": null, - "properties": { - "area_id": 3568.0, - "category": "Best", - "city": "New Haven", - "city_survey": true, - "commercial": false, - "fill": "#76a865", - "grade": "A", - "industrial": false, - "label": "A2", - "residential": true, - "state": "CT" - }, - "type": "Feature" - }, - { - "bbox": null, - "geometry": { - "bbox": null, - "coordinates": [ - [ - [ - [ - -72.90981, - 41.35949 - ], - [ - -72.90284, - 41.35795 - ], - [ - -72.90178, - 41.36076 - ], - [ - -72.90132, - 41.36172 - ], - [ - -72.90069, - 41.36266 - ], - [ - -72.90062, - 41.36305 - ], - [ - -72.90078, - 41.36333 - ], - [ - -72.90073, - 41.36396 - ], - [ - -72.90102, - 41.36493 - ], - [ - -72.90804, - 41.36644 - ], - [ - -72.90644, - 41.37108 - ], - [ - -72.90768, - 41.37158 - ], - [ - -72.90794, - 41.37081 - ], - [ - -72.90844, - 41.37044 - ], - [ - -72.90842, - 41.36985 - ], - [ - -72.90868, - 41.36905 - ], - [ - -72.90935, - 41.3684 - ], - [ - -72.91134, - 41.36756 - ], - [ - -72.9125, - 41.36733 - ], - [ - -72.91544, - 41.36673 - ], - [ - -72.91561, - 41.36632 - ], - [ - -72.91753, - 41.36557 - ], - [ - -72.91818, - 41.36519 - ], - [ - -72.91832, - 41.36462 - ], - [ - -72.91839, - 41.3642 - ], - [ - -72.91383, - 41.36306 - ], - [ - -72.91114, - 41.36279 - ], - [ - -72.90932, - 41.36237 - ], - [ - -72.9096, - 41.35969 - ], - [ - -72.90981, - 41.35949 - ] - ] - ] - ], - "type": "MultiPolygon" - }, - "id": null, - "properties": { - "area_id": 3566.0, - "category": "Best", - "city": "New Haven", - "city_survey": true, - "commercial": false, - "fill": "#76a865", - "grade": "A", - "industrial": false, - "label": "A3", - "residential": true, - "state": "CT" - }, - "type": "Feature" - }, - { - "bbox": null, - "geometry": { - "bbox": null, - "coordinates": [ - [ - [ - [ - -72.90284, - 41.35795 - ], - [ - -72.8999, - 41.35712 - ], - [ - -72.89788, - 41.36147 - ], - [ - -72.89564, - 41.36069 - ], - [ - -72.89401, - 41.36331 - ], - [ - -72.90102, - 41.36493 - ], - [ - -72.90073, - 41.36396 - ], - [ - -72.90078, - 41.36333 - ], - [ - -72.90062, - 41.36305 - ], - [ - -72.90069, - 41.36266 - ], - [ - -72.90132, - 41.36172 - ], - [ - -72.90178, - 41.36076 - ], - [ - -72.90284, - 41.35795 - ] - ] - ] - ], - "type": "MultiPolygon" - }, - "id": null, - "properties": { - "area_id": 3567.0, - "category": "Best", - "city": "New Haven", - "city_survey": true, - "commercial": false, - "fill": "#76a865", - "grade": "A", - "industrial": false, - "label": "A4", - "residential": true, - "state": "CT" - }, - "type": "Feature" - }, - { - "bbox": null, - "geometry": { - "bbox": null, - "coordinates": [ - [ - [ - [ - -72.89801, - 41.3493 - ], - [ - -72.89883, - 41.34937 - ], - [ - -72.9027, - 41.35013 - ], - [ - -72.90569, - 41.34575 - ], - [ - -72.90976, - 41.34024 - ], - [ - -72.90956, - 41.33932 - ], - [ - -72.90925, - 41.33868 - ], - [ - -72.90892, - 41.33903 - ], - [ - -72.90857, - 41.33906 - ], - [ - -72.90721, - 41.33878 - ], - [ - -72.90567, - 41.34024 - ], - [ - -72.90249, - 41.33966 - ], - [ - -72.90192, - 41.33845 - ], - [ - -72.89924, - 41.34017 - ], - [ - -72.89837, - 41.34152 - ], - [ - -72.90214, - 41.34213 - ], - [ - -72.90237, - 41.3438 - ], - [ - -72.90121, - 41.34359 - ], - [ - -72.90016, - 41.34354 - ], - [ - -72.89875, - 41.34364 - ], - [ - -72.89792, - 41.34374 - ], - [ - -72.89748, - 41.34626 - ], - [ - -72.89735, - 41.34745 - ], - [ - -72.89731, - 41.34854 - ], - [ - -72.89758, - 41.34911 - ], - [ - -72.89801, - 41.3493 - ] - ] - ] - ], - "type": "MultiPolygon" - }, - "id": null, - "properties": { - "area_id": 3564.0, - "category": "Best", - "city": "New Haven", - "city_survey": true, - "commercial": false, - "fill": "#76a865", - "grade": "A", - "industrial": false, - "label": "A5", - "residential": true, - "state": "CT" - }, - "type": "Feature" - } - ], - "type": "FeatureCollection" - }, - "path": null, - "valid": null - }, - "type": "GeoJSONSource" - }, - "2db53792-10a5-4c0d-8c38-417877eaa4ed": { - "name": "GeoJSON Layer Source", - "parameters": { - "data": { - "bbox": null, - "features": [ - { - "bbox": null, - "geometry": { - "bbox": null, - "coordinates": [ - [ - [ - [ - -72.9, - 41.36885 - ], - [ - -72.90325, - 41.36999 - ], - [ - -72.90644, - 41.37108 - ], - [ - -72.90804, - 41.36644 - ], - [ - -72.90102, - 41.36493 - ], - [ - -72.89904, - 41.36851 - ], - [ - -72.9, - 41.36885 - ] - ] - ] - ], - "type": "MultiPolygon" - }, - "id": null, - "properties": { - "area_id": 3569.0, - "category": "Best", - "city": "New Haven", - "city_survey": true, - "commercial": false, - "fill": "#76a865", - "grade": "A", - "industrial": false, - "label": "A1", - "residential": true, - "state": "CT" - }, - "type": "Feature" - }, - { - "bbox": null, - "geometry": { - "bbox": null, - "coordinates": [ - [ - [ - [ - -72.89401, - 41.36331 - ], - [ - -72.89256, - 41.36574 - ], - [ - -72.89904, - 41.36851 - ], - [ - -72.90102, - 41.36493 - ], - [ - -72.89401, - 41.36331 - ] - ] - ] - ], - "type": "MultiPolygon" - }, - "id": null, - "properties": { - "area_id": 3568.0, - "category": "Best", - "city": "New Haven", - "city_survey": true, - "commercial": false, - "fill": "#76a865", - "grade": "A", - "industrial": false, - "label": "A2", - "residential": true, - "state": "CT" - }, - "type": "Feature" - }, - { - "bbox": null, - "geometry": { - "bbox": null, - "coordinates": [ - [ - [ - [ - -72.90981, - 41.35949 - ], - [ - -72.90284, - 41.35795 - ], - [ - -72.90178, - 41.36076 - ], - [ - -72.90132, - 41.36172 - ], - [ - -72.90069, - 41.36266 - ], - [ - -72.90062, - 41.36305 - ], - [ - -72.90078, - 41.36333 - ], - [ - -72.90073, - 41.36396 - ], - [ - -72.90102, - 41.36493 - ], - [ - -72.90804, - 41.36644 - ], - [ - -72.90644, - 41.37108 - ], - [ - -72.90768, - 41.37158 - ], - [ - -72.90794, - 41.37081 - ], - [ - -72.90844, - 41.37044 - ], - [ - -72.90842, - 41.36985 - ], - [ - -72.90868, - 41.36905 - ], - [ - -72.90935, - 41.3684 - ], - [ - -72.91134, - 41.36756 - ], - [ - -72.9125, - 41.36733 - ], - [ - -72.91544, - 41.36673 - ], - [ - -72.91561, - 41.36632 - ], - [ - -72.91753, - 41.36557 - ], - [ - -72.91818, - 41.36519 - ], - [ - -72.91832, - 41.36462 - ], - [ - -72.91839, - 41.3642 - ], - [ - -72.91383, - 41.36306 - ], - [ - -72.91114, - 41.36279 - ], - [ - -72.90932, - 41.36237 - ], - [ - -72.9096, - 41.35969 - ], - [ - -72.90981, - 41.35949 - ] - ] - ] - ], - "type": "MultiPolygon" - }, - "id": null, - "properties": { - "area_id": 3566.0, - "category": "Best", - "city": "New Haven", - "city_survey": true, - "commercial": false, - "fill": "#76a865", - "grade": "A", - "industrial": false, - "label": "A3", - "residential": true, - "state": "CT" - }, - "type": "Feature" - }, - { - "bbox": null, - "geometry": { - "bbox": null, - "coordinates": [ - [ - [ - [ - -72.90284, - 41.35795 - ], - [ - -72.8999, - 41.35712 - ], - [ - -72.89788, - 41.36147 - ], - [ - -72.89564, - 41.36069 - ], - [ - -72.89401, - 41.36331 - ], - [ - -72.90102, - 41.36493 - ], - [ - -72.90073, - 41.36396 - ], - [ - -72.90078, - 41.36333 - ], - [ - -72.90062, - 41.36305 - ], - [ - -72.90069, - 41.36266 - ], - [ - -72.90132, - 41.36172 - ], - [ - -72.90178, - 41.36076 - ], - [ - -72.90284, - 41.35795 - ] - ] - ] - ], - "type": "MultiPolygon" - }, - "id": null, - "properties": { - "area_id": 3567.0, - "category": "Best", - "city": "New Haven", - "city_survey": true, - "commercial": false, - "fill": "#76a865", - "grade": "A", - "industrial": false, - "label": "A4", - "residential": true, - "state": "CT" - }, - "type": "Feature" - }, - { - "bbox": null, - "geometry": { - "bbox": null, - "coordinates": [ - [ - [ - [ - -72.89801, - 41.3493 - ], - [ - -72.89883, - 41.34937 - ], - [ - -72.9027, - 41.35013 - ], - [ - -72.90569, - 41.34575 - ], - [ - -72.90976, - 41.34024 - ], - [ - -72.90956, - 41.33932 - ], - [ - -72.90925, - 41.33868 - ], - [ - -72.90892, - 41.33903 - ], - [ - -72.90857, - 41.33906 - ], - [ - -72.90721, - 41.33878 - ], - [ - -72.90567, - 41.34024 - ], - [ - -72.90249, - 41.33966 - ], - [ - -72.90192, - 41.33845 - ], - [ - -72.89924, - 41.34017 - ], - [ - -72.89837, - 41.34152 - ], - [ - -72.90214, - 41.34213 - ], - [ - -72.90237, - 41.3438 - ], - [ - -72.90121, - 41.34359 - ], - [ - -72.90016, - 41.34354 - ], - [ - -72.89875, - 41.34364 - ], - [ - -72.89792, - 41.34374 - ], - [ - -72.89748, - 41.34626 - ], - [ - -72.89735, - 41.34745 - ], - [ - -72.89731, - 41.34854 - ], - [ - -72.89758, - 41.34911 - ], - [ - -72.89801, - 41.3493 - ] - ] - ] - ], - "type": "MultiPolygon" - }, - "id": null, - "properties": { - "area_id": 3564.0, - "category": "Best", - "city": "New Haven", - "city_survey": true, - "commercial": false, - "fill": "#76a865", - "grade": "A", - "industrial": false, - "label": "A5", - "residential": true, - "state": "CT" - }, - "type": "Feature" - } - ], - "type": "FeatureCollection" - }, - "path": null, - "valid": null - }, - "type": "GeoJSONSource" - }, - "8f3f22ce-de8b-4a2b-8ca2-c371e717eebc": { - "name": "Raster Layer Source", - "parameters": { - "attribution": "", - "bounds": [], - "htmlAttribution": "", - "maxZoom": 24.0, - "minZoom": 0.0, - "provider": "", - "url": "https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}", - "urlParameters": {} - }, - "type": "RasterSource" - }, - "99f813a4-730a-4682-9a01-6711d0048330": { - "name": "Raster Layer Source", - "parameters": { - "attribution": "", - "bounds": [], - "htmlAttribution": "", - "maxZoom": 24.0, - "minZoom": 0.0, - "provider": "", - "url": "https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}", - "urlParameters": {} - }, - "type": "RasterSource" - } - } -} diff --git a/examples/espm-157/new_haven.json b/examples/espm-157/new_haven.json deleted file mode 100644 index 7fe3f00b0..000000000 --- a/examples/espm-157/new_haven.json +++ /dev/null @@ -1,11 +0,0 @@ -{ -"type": "FeatureCollection", -"name": "new_haven", -"features": [ -{ "type": "Feature", "properties": { "area_id": 3569, "city": "New Haven", "state": "CT", "city_survey": true, "category": "Best", "grade": "A", "label": "A1", "residential": true, "commercial": false, "industrial": false, "fill": "#76a865" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -72.9, 41.36885 ], [ -72.90325, 41.36999 ], [ -72.90644, 41.37108 ], [ -72.90804, 41.36644 ], [ -72.90102, 41.36493 ], [ -72.89904, 41.36851 ], [ -72.9, 41.36885 ] ] ] ] } }, -{ "type": "Feature", "properties": { "area_id": 3568, "city": "New Haven", "state": "CT", "city_survey": true, "category": "Best", "grade": "A", "label": "A2", "residential": true, "commercial": false, "industrial": false, "fill": "#76a865" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -72.89401, 41.36331 ], [ -72.89256, 41.36574 ], [ -72.89904, 41.36851 ], [ -72.90102, 41.36493 ], [ -72.89401, 41.36331 ] ] ] ] } }, -{ "type": "Feature", "properties": { "area_id": 3566, "city": "New Haven", "state": "CT", "city_survey": true, "category": "Best", "grade": "A", "label": "A3", "residential": true, "commercial": false, "industrial": false, "fill": "#76a865" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -72.90981, 41.35949 ], [ -72.90284, 41.35795 ], [ -72.90178, 41.36076 ], [ -72.90132, 41.36172 ], [ -72.90069, 41.36266 ], [ -72.90062, 41.36305 ], [ -72.90078, 41.36333 ], [ -72.90073, 41.36396 ], [ -72.90102, 41.36493 ], [ -72.90804, 41.36644 ], [ -72.90644, 41.37108 ], [ -72.90768, 41.37158 ], [ -72.90794, 41.37081 ], [ -72.90844, 41.37044 ], [ -72.90842, 41.36985 ], [ -72.90868, 41.36905 ], [ -72.90935, 41.3684 ], [ -72.91134, 41.36756 ], [ -72.9125, 41.36733 ], [ -72.91544, 41.36673 ], [ -72.91561, 41.36632 ], [ -72.91753, 41.36557 ], [ -72.91818, 41.36519 ], [ -72.91832, 41.36462 ], [ -72.91839, 41.3642 ], [ -72.91383, 41.36306 ], [ -72.91114, 41.36279 ], [ -72.90932, 41.36237 ], [ -72.9096, 41.35969 ], [ -72.90981, 41.35949 ] ] ] ] } }, -{ "type": "Feature", "properties": { "area_id": 3567, "city": "New Haven", "state": "CT", "city_survey": true, "category": "Best", "grade": "A", "label": "A4", "residential": true, "commercial": false, "industrial": false, "fill": "#76a865" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -72.90284, 41.35795 ], [ -72.8999, 41.35712 ], [ -72.89788, 41.36147 ], [ -72.89564, 41.36069 ], [ -72.89401, 41.36331 ], [ -72.90102, 41.36493 ], [ -72.90073, 41.36396 ], [ -72.90078, 41.36333 ], [ -72.90062, 41.36305 ], [ -72.90069, 41.36266 ], [ -72.90132, 41.36172 ], [ -72.90178, 41.36076 ], [ -72.90284, 41.35795 ] ] ] ] } }, -{ "type": "Feature", "properties": { "area_id": 3564, "city": "New Haven", "state": "CT", "city_survey": true, "category": "Best", "grade": "A", "label": "A5", "residential": true, "commercial": false, "industrial": false, "fill": "#76a865" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -72.89801, 41.3493 ], [ -72.89883, 41.34937 ], [ -72.9027, 41.35013 ], [ -72.90569, 41.34575 ], [ -72.90976, 41.34024 ], [ -72.90956, 41.33932 ], [ -72.90925, 41.33868 ], [ -72.90892, 41.33903 ], [ -72.90857, 41.33906 ], [ -72.90721, 41.33878 ], [ -72.90567, 41.34024 ], [ -72.90249, 41.33966 ], [ -72.90192, 41.33845 ], [ -72.89924, 41.34017 ], [ -72.89837, 41.34152 ], [ -72.90214, 41.34213 ], [ -72.90237, 41.3438 ], [ -72.90121, 41.34359 ], [ -72.90016, 41.34354 ], [ -72.89875, 41.34364 ], [ -72.89792, 41.34374 ], [ -72.89748, 41.34626 ], [ -72.89735, 41.34745 ], [ -72.89731, 41.34854 ], [ -72.89758, 41.34911 ], [ -72.89801, 41.3493 ] ] ] ] } } -] -} diff --git a/examples/espm-157/spatial-1.ipynb b/examples/espm-157/spatial-1.ipynb deleted file mode 100644 index 1a762a55f..000000000 --- a/examples/espm-157/spatial-1.ipynb +++ /dev/null @@ -1,273 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "c3f4096f-cbd3-43e8-a986-520681f03581", - "metadata": {}, - "source": [ - "# ESPM 157 - Intro to Spatial Data\n", - "\n", - "\n", - "\n", - "Install dependencies:\n", - "\n", - "```bash\n", - "micromamba install geopandas ibis-duckdb\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "execution_state": "idle", - "id": "b76ae1f0-334e-41c0-9533-407c879b4ad6", - "metadata": {}, - "outputs": [], - "source": [ - "import ibis\n", - "\n", - "con = ibis.duckdb.connect()" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "execution_state": "idle", - "id": "837aa4e0-5eeb-48c1-97ce-3f8706503911", - "metadata": {}, - "outputs": [], - "source": [ - "redlines = con.read_geo(\"/vsicurl/https://dsl.richmond.edu/panorama/redlining/static/mappinginequality.gpkg\")" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "execution_state": "idle", - "id": "5c91dfaa-075c-401f-adeb-18bed678103b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
city
0Denver
1Miami
2St. Petersburg
3Waterloo
4Peoria
5South Bend
6Junction City
7Belmont
8Fall River
9Holyoke Chicopee
\n", - "
" - ], - "text/plain": [ - " city\n", - "0 Denver\n", - "1 Miami\n", - "2 St. Petersburg\n", - "3 Waterloo\n", - "4 Peoria\n", - "5 South Bend\n", - "6 Junction City\n", - "7 Belmont\n", - "8 Fall River\n", - "9 Holyoke Chicopee" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# First 10 distinct cities\n", - "redlines.select(redlines.city).distinct().head(10).execute()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "execution_state": "idle", - "id": "4d3319fa-b3b4-4931-b073-98b649e41b65", - "metadata": {}, - "outputs": [], - "source": [ - "city = redlines.filter(redlines.city == \"New Haven\")" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "execution_state": "idle", - "id": "34f7f4d6-28d6-4716-8e03-ac32c6ae3bb7", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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goODMWnSJHz77beIjIwE0DWmu6CgADt37kRTUxNCQ0Mxc+ZMvP/++1CpVO6+TEII4YyIMca4LoIvjEYj1Go1DAYD/Pz8uC6HEDLE9DWDaK4SQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRGApuQggRmH4Fd1ZWFkQiEZ555hn7tuzsbKSlpSEoKAgikQj5+fl3PE52djZSUlLg7+8PpVKJ5ORk7Nq1y2GfzMxMiEQihw+tVuuwD2MMmZmZCAsLg1wuR2pqKs6fP9+fSySEEN5xObhzcnKwdetWJCUlOWw3m82YOnUqXnnllT4fKyAgAOvXr8fJkydx9uxZpKenIz09Hfv373fY7+6770Z1dbX9o6CgwOH91157DW+88QY2b96MnJwcaLVazJkzB83Nza5eJiGE8I6XK59kMpmwZMkSbNu2DRs3bnR4b+nSpQCAkpKSPh8vNTXV4fWaNWuwY8cOHD9+HGlpaTeL9fLq1sq+gTGGN998E+vXr8eCBQsAADt27EBISAjeffddrFy5ss/1EEIIn7nU4l69ejXmzp2L2bNnu7seMMZw+PBhFBUVYfr06Q7vXb58GWFhYYiOjsbixYtx7do1+3vFxcXQ6XS4//777dtkMhlmzJiBEydO9Hgui8UCo9Ho8EGIkPzuwzNI+/MxZHyQj+3fFCOnpAEt1g6uyyIDzOkW9549e5CXl4ecnBy3FmIwGBAeHg6LxQKJRIK3334bc+bMsb8/ceJE7Ny5EyNHjkRNTQ02btyIKVOm4Pz58wgMDIROpwMAhISEOBw3JCQEpaWlPZ4zKysLL774oluvg5DB8sU5HT7MrQAAFNU0IzuvEgAgFgHRQUokhquRGKZGYrgad4f7wc/Hm8tyiRs5Fdzl5eVYs2YNDhw4AB8fH7cWolKpkJ+fD5PJhMOHDyMj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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "city_gdf = city.head().execute()\n", - "city_gdf.plot()" - ] - }, - { - "cell_type": "markdown", - "id": "d37a610a-1444-43a3-900f-dfe16a890ab9", - "metadata": {}, - "source": [ - "## OK, but what about spatial context?\n", - "\n", - "I want to explore this data more interactively." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "execution_state": "idle", - "id": "94875994-926a-46b4-be29-be7c676a9905", - "metadata": {}, - "outputs": [], - "source": [ - "# from jupytergis_lab import GISDocument\n", - "\n", - "# doc = GISDocument(\"./debug.jGIS\")\n", - "# doc" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "execution_state": "idle", - "id": "642e9281-d9d5-4f35-a7f6-65bf1096cb1f", - "metadata": {}, - "outputs": [], - "source": [ - "# city_gdf.to_file(\"new_haven.json\")" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "execution_state": "idle", - "id": "6e68aaac-dfc0-42d8-a3b9-05fce59524b2", - "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.ywidget-view+json": { - "model_id": "17493598bcbe4db1b80e0b5f0273bf3d", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from jupytergis_lab.notebook.geo_debug import geo_debug\n", - "\n", - "\n", - "geo_debug(\"new_haven.json\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.8" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/espm-157/spatial-1.jGIS b/examples/espm-157/spatial-1.jGIS deleted file mode 100644 index fa6818ffc..000000000 --- a/examples/espm-157/spatial-1.jGIS +++ /dev/null @@ -1,83 +0,0 @@ -{ - "layerTree": [ - "3d605d1b-7a7b-4e8e-be88-75ac4e5d31c7", - "01ed0c0a-d3d2-441c-9d46-fbd1bb191e38", - "31b41fa3-c8a5-4e25-9853-73b4f788da0c" - ], - "layers": { - "01ed0c0a-d3d2-441c-9d46-fbd1bb191e38": { - "name": "Custom GeoJSON Layer", - "parameters": { - "opacity": 1.0, - "source": "5d31501d-e03a-4798-bcae-a37a5fbe7237", - "type": "line" - }, - "type": "VectorLayer", - "visible": true - }, - "31b41fa3-c8a5-4e25-9853-73b4f788da0c": { - "name": "USGS.USTopo Layer", - "parameters": { - "source": "a21cb39a-9174-4cc4-9362-1d46f35d56b8" - }, - "type": "RasterLayer", - "visible": true - }, - "3d605d1b-7a7b-4e8e-be88-75ac4e5d31c7": { - "name": "OpenStreetMap.Mapnik Layer", - "parameters": { - "source": "72f468ff-6b37-4893-986a-d7bae1f16242" - }, - "type": "RasterLayer", - "visible": true - } - }, - "metadata": {}, - "options": { - "bearing": 0.0, - "extent": [ - -8967908.753904685, - 4200125.391526781, - -6903204.837588707, - 5471494.798125289 - ], - "latitude": 39.79233083947287, - "longitude": -71.28631957617792, - "pitch": 0.0, - "projection": "EPSG:3857", - "zoom": 6.265960317603158 - }, - "sources": { - "5d31501d-e03a-4798-bcae-a37a5fbe7237": { - "name": "Custom GeoJSON Layer Source", - "parameters": { - "path": "new_haven.json" - }, - "type": "GeoJSONSource" - }, - "72f468ff-6b37-4893-986a-d7bae1f16242": { - "name": "OpenStreetMap.Mapnik", - "parameters": { - "attribution": "(C) OpenStreetMap contributors", - "maxZoom": 19.0, - "minZoom": 0.0, - "provider": "OpenStreetMap", - "url": "https://tile.openstreetmap.org/{z}/{x}/{y}.png", - "urlParameters": {} - }, - "type": "RasterSource" - }, - "a21cb39a-9174-4cc4-9362-1d46f35d56b8": { - "name": "USGS.USTopo", - "parameters": { - "attribution": "Tiles courtesy of the U.S. Geological Survey", - "maxZoom": 20.0, - "minZoom": 0.0, - "provider": "USGS", - "url": "https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}", - "urlParameters": {} - }, - "type": "RasterSource" - } - } -} diff --git a/examples/explore.ipynb b/examples/explore.ipynb new file mode 100644 index 000000000..afcb39600 --- /dev/null +++ b/examples/explore.ipynb @@ -0,0 +1,45 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "3c789a1b-328a-4cfa-b6b0-a4bb8acdc27f", + "metadata": {}, + "outputs": [], + "source": [ + "from jupytergis import explore\n", + "\n", + "explore(\"eq.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e144d26f-e35e-4b73-a99b-1f9dd2f73b45", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From a565ff45e1cbdc9615eec1ad6974cd0a3c594770 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Thu, 17 Apr 2025 14:12:31 -0600 Subject: [PATCH 25/46] Cleanup OBE comment --- python/jupytergis_lab/jupytergis_lab/explore.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/explore.py b/python/jupytergis_lab/jupytergis_lab/explore.py index 6a0f691ab..f494c2ba1 100644 --- a/python/jupytergis_lab/jupytergis_lab/explore.py +++ b/python/jupytergis_lab/jupytergis_lab/explore.py @@ -22,8 +22,7 @@ def explore(geojson_path: str | Path) -> None: # TODO: Zoom to layer; is that feasible to do from Python? Currently not exposed in # Python API. - # FIXME: The document opens as intended, but the file has no contents and any - # updates performed result in no update to the file. + sidecar = Sidecar( title="JupyterGIS explorer", anchor="split-right", From 41f0a5e2844ff000eda5186ecbd70a65f51ee05e Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Thu, 17 Apr 2025 14:16:27 -0600 Subject: [PATCH 26/46] Remove OBE test --- .../jupytergis_lab/notebook/tests/test_api.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/tests/test_api.py b/python/jupytergis_lab/jupytergis_lab/notebook/tests/test_api.py index fee6a182c..9cfdc17c9 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/tests/test_api.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/tests/test_api.py @@ -44,15 +44,3 @@ def test_add_and_remove_layer_and_source(self): def test_remove_nonexistent_layer_raises(self): with pytest.raises(KeyError): self.doc.remove_layer("foo") - - -def test_untitled_doc(tmp_path): - os.chdir(tmp_path) - - GISDocument() - assert len(list(tmp_path.iterdir())) == 1 - assert (tmp_path / "untitled.jGIS").is_file() - - GISDocument() - assert len(list(tmp_path.iterdir())) == 2 - assert (tmp_path / "untitled1.jGIS").is_file() From d53117849b7fe858a99a7950cc49333416050f88 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Fri, 18 Apr 2025 20:21:43 -0600 Subject: [PATCH 27/46] Extract sidecar-opening to GISDocument API User can open sidecar with one line of code. They can learn how to do it from reading our API docs only. --- .../jupytergis_lab/jupytergis_lab/explore.py | 9 +------- .../jupytergis_lab/notebook/gis_document.py | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/explore.py b/python/jupytergis_lab/jupytergis_lab/explore.py index f494c2ba1..e44a9b124 100644 --- a/python/jupytergis_lab/jupytergis_lab/explore.py +++ b/python/jupytergis_lab/jupytergis_lab/explore.py @@ -1,7 +1,5 @@ from pathlib import Path -from sidecar import Sidecar - from jupytergis_lab import GISDocument @@ -23,9 +21,4 @@ def explore(geojson_path: str | Path) -> None: # TODO: Zoom to layer; is that feasible to do from Python? Currently not exposed in # Python API. - sidecar = Sidecar( - title="JupyterGIS explorer", - anchor="split-right", - ) - with sidecar: - display(doc) + doc.sidecar(title="JupyterGIS explorer") diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py index 8fc239db3..dce4cd70f 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py @@ -9,6 +9,7 @@ from pycrdt import Array, Doc, Map from pydantic import BaseModel +from sidecar import Sidecar from ypywidgets.comm import CommWidget from .objects import ( @@ -106,6 +107,28 @@ def layer_tree(self) -> List[str | Dict]: """ return self._layerTree.to_py() + def sidecar( + self, + *, + title: str = "JupyterGIS sidecar", + anchor: Literal[ + "split-right", + "split-left", + "split-top", + "split-bottom", + "tab-before", + "tab-after", + "right", + ] = "split-right", + ): + """Open the document in a new sidecar panel. + + :param anchor: Where to position the new sidecar panel. + """ + sidecar = Sidecar(title=title, anchor=anchor) + with sidecar: + display(self) + def export_to_qgis(self, path: str | Path) -> bool: # Lazy import, jupytergis_qgis of qgis may not be installed from jupytergis_qgis.qgis_loader import export_project_to_qgis From b649e4416e97ed2f11f4a6145eec4b05dda4cfb2 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Fri, 18 Apr 2025 20:24:34 -0600 Subject: [PATCH 28/46] Dynamically determine type of object passed to explore() Currently only support geodataframes and GeoJSON. --- .../jupytergis_lab/jupytergis_lab/explore.py | 59 ++++++++++++++++--- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/explore.py b/python/jupytergis_lab/jupytergis_lab/explore.py index e44a9b124..c5570a9f6 100644 --- a/python/jupytergis_lab/jupytergis_lab/explore.py +++ b/python/jupytergis_lab/jupytergis_lab/explore.py @@ -1,24 +1,69 @@ from pathlib import Path +from typing import Any, Literal +import re from jupytergis_lab import GISDocument -def explore(geojson_path: str | Path) -> None: +def explore(data: str | Path | Any) -> GISDocument: """Run a JupyterGIS data interaction interface alongside a Notebook. - :param geojson_path: Path to a GeoJSON file. + :param data: A GeoDataFrame or path to a GeoJSON file. + + :raises FileNotFoundException: User passed a file that isn't present. + :raises NotImplementedError: User passed an input value that isn't supported yet. + :raises TypeError: User passed an object type that isn't supported. + :raises ValueError: User passed an object value that isn't supported. """ doc = GISDocument() - # TODO: Basemap choices + # TODO: Basic basemap choices, e.g. add parameter `basemap: Literal["dark", "light"]` doc.add_raster_layer( "https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}" ) - # TODO: Support lots of file types, and support Python objects like geodataframes. - doc.add_geojson_layer(str(geojson_path)) + # TODO: Extract to GISDocument class + _add_layer(doc, data) - # TODO: Zoom to layer; is that feasible to do from Python? Currently not exposed in - # Python API. + # TODO: Zoom to layer. Currently not exposed in Python API. doc.sidecar(title="JupyterGIS explorer") + + # TODO: should we return `doc`? It enables the exploration environment more usable, + # but by default, `explore(...)` would display a widget in the notebook _and_ open a + # sidecar for the same widget. The user would need to append a semicolon to disable + # that behavior. We can't disable that behavior from within this function to the + # best of my knowlwedge. + + +def _add_layer(doc: GISDocument, data: str | Path | Any) -> None: + if isinstance(data, str): + if re.match(r"^(http|https)://", data) is not None: + raise NotImplementedError("URLs not yet supported.") + else: + data = Path(data) + + if isinstance(data, Path): + if not data.exists(): + raise FileNotFoundError(f"File not found: {data}") + + ext = data.suffix.lower() + + if ext in [".geojson", ".json"]: + doc.add_geojson_layer(path=data) + return + # TODO: elif ext in ['.tif', '.tiff']: + else: + raise ValueError(f"Unknown file type: {data}") + + try: + from geopandas import GeoDataFrame + + if isinstance(data, GeoDataFrame): + print(type(data.to_geo_dict())) + doc.add_geojson_layer(data=data.to_geo_dict()) + return + except ImportError: + pass + + raise TypeError(f"Unsupported input type: {type(data)}") From e8d054f218641d5b2b1cd4ea61e0b464989df6f5 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Fri, 18 Apr 2025 20:28:18 -0600 Subject: [PATCH 29/46] Update example notebook to explore a geodataframe --- examples/explore.ipynb | 78 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/examples/explore.ipynb b/examples/explore.ipynb index afcb39600..f9068dd02 100644 --- a/examples/explore.ipynb +++ b/examples/explore.ipynb @@ -1,24 +1,90 @@ { "cells": [ + { + "cell_type": "markdown", + "id": "c3f4096f-cbd3-43e8-a986-520681f03581", + "metadata": {}, + "source": [ + "# ESPM 157 - Intro to Spatial Data\n", + "\n", + "\n", + "\n", + "Install dependencies:\n", + "\n", + "```bash\n", + "micromamba install geopandas ibis-duckdb\n", + "```" + ] + }, { "cell_type": "code", - "execution_count": 1, - "id": "3c789a1b-328a-4cfa-b6b0-a4bb8acdc27f", + "execution_count": null, + "id": "b76ae1f0-334e-41c0-9533-407c879b4ad6", "metadata": {}, "outputs": [], "source": [ - "from jupytergis import explore\n", + "import ibis\n", + "\n", + "con = ibis.duckdb.connect()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "837aa4e0-5eeb-48c1-97ce-3f8706503911", + "metadata": {}, + "outputs": [], + "source": [ + "redlines = con.read_geo(\n", + " \"/vsicurl/https://dsl.richmond.edu/panorama/redlining/static/mappinginequality.gpkg\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4d3319fa-b3b4-4931-b073-98b649e41b65", + "metadata": {}, + "outputs": [], + "source": [ + "city = redlines.filter(redlines.city == \"New Haven\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34f7f4d6-28d6-4716-8e03-ac32c6ae3bb7", + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "city_gdf = city.head().execute()\n", + "city_gdf.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "d37a610a-1444-43a3-900f-dfe16a890ab9", + "metadata": {}, + "source": [ + "## OK, but what about spatial context?\n", "\n", - "explore(\"eq.json\")" + "I want to explore this data more interactively." ] }, { "cell_type": "code", "execution_count": null, - "id": "e144d26f-e35e-4b73-a99b-1f9dd2f73b45", + "id": "6e68aaac-dfc0-42d8-a3b9-05fce59524b2", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "from jupytergis import explore\n", + "\n", + "# Open a new exploration window\n", + "explore(city_gdf)" + ] } ], "metadata": { From d4d0de01d0204b6e50a13a75ea99e939d7642e82 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Fri, 18 Apr 2025 20:28:58 -0600 Subject: [PATCH 30/46] Remove debugging print statement --- python/jupytergis_lab/jupytergis_lab/explore.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/jupytergis_lab/jupytergis_lab/explore.py b/python/jupytergis_lab/jupytergis_lab/explore.py index c5570a9f6..ad695817b 100644 --- a/python/jupytergis_lab/jupytergis_lab/explore.py +++ b/python/jupytergis_lab/jupytergis_lab/explore.py @@ -60,7 +60,6 @@ def _add_layer(doc: GISDocument, data: str | Path | Any) -> None: from geopandas import GeoDataFrame if isinstance(data, GeoDataFrame): - print(type(data.to_geo_dict())) doc.add_geojson_layer(data=data.to_geo_dict()) return except ImportError: From e621cd8ee2755386a536232118574a300ac93be8 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Sat, 19 Apr 2025 15:14:50 -0600 Subject: [PATCH 31/46] Move "smart" layer add into GISDocument method --- .../jupytergis_lab/jupytergis_lab/explore.py | 44 +++------------ .../jupytergis_lab/notebook/gis_document.py | 53 ++++++++++++++++++- 2 files changed, 59 insertions(+), 38 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/explore.py b/python/jupytergis_lab/jupytergis_lab/explore.py index ad695817b..44ebd76ec 100644 --- a/python/jupytergis_lab/jupytergis_lab/explore.py +++ b/python/jupytergis_lab/jupytergis_lab/explore.py @@ -1,11 +1,14 @@ from pathlib import Path -from typing import Any, Literal -import re +from typing import Any, Optional from jupytergis_lab import GISDocument -def explore(data: str | Path | Any) -> GISDocument: +def explore( + data: str | Path | Any, + *, + layer_name: Optional[str] = None, +) -> GISDocument: """Run a JupyterGIS data interaction interface alongside a Notebook. :param data: A GeoDataFrame or path to a GeoJSON file. @@ -22,8 +25,7 @@ def explore(data: str | Path | Any) -> GISDocument: "https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}" ) - # TODO: Extract to GISDocument class - _add_layer(doc, data) + doc.add_layer(data, name=layer_name) # TODO: Zoom to layer. Currently not exposed in Python API. @@ -34,35 +36,3 @@ def explore(data: str | Path | Any) -> GISDocument: # sidecar for the same widget. The user would need to append a semicolon to disable # that behavior. We can't disable that behavior from within this function to the # best of my knowlwedge. - - -def _add_layer(doc: GISDocument, data: str | Path | Any) -> None: - if isinstance(data, str): - if re.match(r"^(http|https)://", data) is not None: - raise NotImplementedError("URLs not yet supported.") - else: - data = Path(data) - - if isinstance(data, Path): - if not data.exists(): - raise FileNotFoundError(f"File not found: {data}") - - ext = data.suffix.lower() - - if ext in [".geojson", ".json"]: - doc.add_geojson_layer(path=data) - return - # TODO: elif ext in ['.tif', '.tiff']: - else: - raise ValueError(f"Unknown file type: {data}") - - try: - from geopandas import GeoDataFrame - - if isinstance(data, GeoDataFrame): - doc.add_geojson_layer(data=data.to_geo_dict()) - return - except ImportError: - pass - - raise TypeError(f"Unsupported input type: {type(data)}") diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py index dce4cd70f..7e77e73fd 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py @@ -3,6 +3,7 @@ import json import logging import os +import re from pathlib import Path from typing import Any, Dict, List, Literal, Optional, Union from uuid import uuid4 @@ -142,6 +143,56 @@ def export_to_qgis(self, path: str | Path) -> bool: return export_project_to_qgis(path, virtual_file) + def add_layer( + self, + data: Any, + *, + name: Optional[str] = None, + attribution: Optional[str] = None, + opacity: float = 1, + ) -> str: + """Add a layer to the document, autodetecting its type. + + This method currently supports only GeoDataFrames and GeoJSON files. + + :param data: A data object. Valid data objects include geopandas GeoDataFrames and paths to GeoJSON files. + + :return: A layer ID string. + + :raises FileNotFoundException: Received a file path that doesn't exist. + :raises NotImplementedError: Received an input value that isn't supported yet. + :raises TypeError: Received an object type that isn't supported. + :raises ValueError: Received an input value that isn't supported. + """ + if isinstance(data, str): + if re.match(r"^(http|https)://", data) is not None: + raise NotImplementedError("URLs not yet supported.") + else: + data = Path(data) + + if isinstance(data, Path): + if not data.exists(): + raise FileNotFoundError(f"File not found: {data}") + + ext = data.suffix.lower() + + if ext in [".geojson", ".json"]: + return self.add_geojson_layer(path=data, name=name) + elif ext in [".tif", ".tiff"]: + raise NotImplementedError("GeoTIFFs not yet supported.") + else: + raise ValueError(f"Unsupported file type: {data}") + + try: + from geopandas import GeoDataFrame + + if isinstance(data, GeoDataFrame): + return self.add_geojson_layer(data=data.to_geo_dict(), name=name) + except ImportError: + pass + + raise TypeError(f"Unsupported input type: {type(data)}") + def add_raster_layer( self, url: str, @@ -745,7 +796,7 @@ def _add_source(self, new_object: "JGISObject"): self._sources[_id] = obj_dict return _id - def _add_layer(self, new_object: "JGISObject"): + def _add_layer(self, new_object: "JGISObject") -> str: _id = str(uuid4()) obj_dict = json.loads(new_object.json()) self._layers[_id] = obj_dict From a29b0c2e383ff6d1ae8f850bc13bbd521680ef93 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Sat, 19 Apr 2025 15:56:13 -0600 Subject: [PATCH 32/46] Add basemap parameter to `explore()` --- examples/explore.ipynb | 2 +- .../jupytergis_lab/jupytergis_lab/explore.py | 47 +++++++++++++++++-- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/examples/explore.ipynb b/examples/explore.ipynb index f9068dd02..cc1398245 100644 --- a/examples/explore.ipynb +++ b/examples/explore.ipynb @@ -83,7 +83,7 @@ "from jupytergis import explore\n", "\n", "# Open a new exploration window\n", - "explore(city_gdf)" + "explore(city_gdf, layer_name=\"New Haven\", basemap=\"dark\")" ] } ], diff --git a/python/jupytergis_lab/jupytergis_lab/explore.py b/python/jupytergis_lab/jupytergis_lab/explore.py index 44ebd76ec..cdc19a8a4 100644 --- a/python/jupytergis_lab/jupytergis_lab/explore.py +++ b/python/jupytergis_lab/jupytergis_lab/explore.py @@ -1,13 +1,52 @@ +from dataclasses import dataclass from pathlib import Path -from typing import Any, Optional +from typing import Any, Literal, Optional from jupytergis_lab import GISDocument +@dataclass +class Basemap: + name: str + url: str + + +BasemapChoice = Literal["light", "dark", "topo"] +_basemaps: dict[BasemapChoice, list[Basemap]] = { + "light": [ + Basemap( + name="ArcGIS dark basemap", + url="https://services.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}.pbf", + ), + Basemap( + name="ArcGIS dark basemap reference", + url="https://services.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Reference/MapServer/tile/{z}/{y}/{x}.pbf", + ), + ], + "dark": [ + Basemap( + name="ArcGIS light basemap", + url="https://services.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Base/MapServer/tile/{z}/{y}/{x}.pbf", + ), + Basemap( + name="ArcGIS light basemap reference", + url="https://services.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Dark_Gray_Reference/MapServer/tile/{z}/{y}/{x}.pbf", + ), + ], + "topo": [ + Basemap( + name="USGS topographic basemap", + url="https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}", + ), + ], +} + + def explore( data: str | Path | Any, *, layer_name: Optional[str] = None, + basemap: BasemapChoice = "topo", ) -> GISDocument: """Run a JupyterGIS data interaction interface alongside a Notebook. @@ -20,10 +59,8 @@ def explore( """ doc = GISDocument() - # TODO: Basic basemap choices, e.g. add parameter `basemap: Literal["dark", "light"]` - doc.add_raster_layer( - "https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}" - ) + for basemap_obj in _basemaps[basemap]: + doc.add_raster_layer(basemap_obj.url, name=basemap_obj.name) doc.add_layer(data, name=layer_name) From a083bdf90cd3ce7474acf5f15ae8bc36c111fca1 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Sat, 19 Apr 2025 16:56:31 -0600 Subject: [PATCH 33/46] Autodoc `explore()` --- docs/user_guide/python_api.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/user_guide/python_api.md b/docs/user_guide/python_api.md index 0a7c56cdf..ba6ed4e49 100644 --- a/docs/user_guide/python_api.md +++ b/docs/user_guide/python_api.md @@ -34,9 +34,15 @@ doc Once the document is opened/created, you can start creating GIS layers. -## `GISDocument` API Reference +## `explore` ```{eval-rst} -.. autoclass:: jupytergis_lab.GISDocument +.. autofuncion:: jupytergis.explore +``` + +## `GISDocument` + +```{eval-rst} +.. autoclass:: jupytergis.GISDocument :members: ``` From a17339f37a5b7d49aaa131c15e5cf033fb67c149 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Sat, 19 Apr 2025 16:58:19 -0600 Subject: [PATCH 34/46] Bugfix: don't pass name=None to methods that don't support it --- python/jupytergis_lab/jupytergis_lab/explore.py | 2 +- .../jupytergis_lab/jupytergis_lab/notebook/gis_document.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/explore.py b/python/jupytergis_lab/jupytergis_lab/explore.py index cdc19a8a4..901b24b0b 100644 --- a/python/jupytergis_lab/jupytergis_lab/explore.py +++ b/python/jupytergis_lab/jupytergis_lab/explore.py @@ -45,7 +45,7 @@ class Basemap: def explore( data: str | Path | Any, *, - layer_name: Optional[str] = None, + layer_name: Optional[str] = "Exploration layer", basemap: BasemapChoice = "topo", ) -> GISDocument: """Run a JupyterGIS data interaction interface alongside a Notebook. diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py index 7e77e73fd..b827651c0 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py @@ -147,7 +147,7 @@ def add_layer( self, data: Any, *, - name: Optional[str] = None, + name: str, attribution: Optional[str] = None, opacity: float = 1, ) -> str: @@ -156,6 +156,9 @@ def add_layer( This method currently supports only GeoDataFrames and GeoJSON files. :param data: A data object. Valid data objects include geopandas GeoDataFrames and paths to GeoJSON files. + :param name: The name that will be used for the layer. + :param attribution: The attribution. + :param opacity: The opacity, between 0 and 1. :return: A layer ID string. From d8a235592240f62a59f5299024e92b0a2d3cd513 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Sat, 19 Apr 2025 17:00:39 -0600 Subject: [PATCH 35/46] Fix incorrect docstring raises directive --- python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py index b827651c0..bae34f5db 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py @@ -162,7 +162,7 @@ def add_layer( :return: A layer ID string. - :raises FileNotFoundException: Received a file path that doesn't exist. + :raises FileNotFoundError: Received a file path that doesn't exist. :raises NotImplementedError: Received an input value that isn't supported yet. :raises TypeError: Received an object type that isn't supported. :raises ValueError: Received an input value that isn't supported. From c32623a39192b931e8d8e757acd6f20323fedee8 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Sat, 19 Apr 2025 18:25:06 -0600 Subject: [PATCH 36/46] Install jupytergis metapackage in docs environment Autogenerated docs will document `jupytergis.GISDocument` and `jupytergis.explore` instead of `jupytergis_lab.GISDocument` and `jupytergis_lab.explore`. As `jupytergis` is the package we instruct end-users to install, documenting `jupytergis_lab` is confusing. --- docs/environment-docs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/environment-docs.yml b/docs/environment-docs.yml index 18d61dcf8..7aca293d1 100644 --- a/docs/environment-docs.yml +++ b/docs/environment-docs.yml @@ -25,3 +25,4 @@ dependencies: # See: https://github.com/geojupyter/jupytergis/issues/585 - ../python/jupytergis_core - ../python/jupytergis_lab + - ../python/jupytergis From 761e94a4c744cbcd43450bf9f660cd2e03c411d3 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Sat, 19 Apr 2025 19:14:37 -0600 Subject: [PATCH 37/46] Fix CSS to properly use vertical space in linked output & sidecar views We expect this change to be eventually included in JupyterLab: https://github.com/jupyterlab/jupyterlab/pull/17487 Including in JGIS for compatibility with versions of JupyterLab that don't include this CSS. --- python/jupytergis_lab/style/base.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/jupytergis_lab/style/base.css b/python/jupytergis_lab/style/base.css index 0a4465603..0e55ed114 100644 --- a/python/jupytergis_lab/style/base.css +++ b/python/jupytergis_lab/style/base.css @@ -419,6 +419,9 @@ div.jGIS-toolbar-widget > div.jp-Toolbar-item:last-child { } /* Support output views, created with "Create new view for cell output" context menu */ +.jp-LinkedOutputView .jp-OutputArea-child:only-child { + height: 100%; +} .jp-LinkedOutputView .jupytergis-notebook-widget { height: 100%; } From baba817485fd17f458646598d05599ac51225401 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Tue, 22 Apr 2025 09:54:51 -0600 Subject: [PATCH 38/46] Fix autodoc typo --- docs/user_guide/python_api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user_guide/python_api.md b/docs/user_guide/python_api.md index ba6ed4e49..0c0e47871 100644 --- a/docs/user_guide/python_api.md +++ b/docs/user_guide/python_api.md @@ -37,7 +37,7 @@ Once the document is opened/created, you can start creating GIS layers. ## `explore` ```{eval-rst} -.. autofuncion:: jupytergis.explore +.. autofunction:: jupytergis.explore ``` ## `GISDocument` From b47a01ece75af121275cab458193a60cfd5a6784 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Tue, 22 Apr 2025 20:25:19 -0600 Subject: [PATCH 39/46] Fix layer panel not updating in sidecar view Co-authored-by: Nicolas Brichet <32258950+brichet@users.noreply.github.com> --- packages/base/src/panelview/components/layers.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/base/src/panelview/components/layers.tsx b/packages/base/src/panelview/components/layers.tsx index 7e7238cac..7e64e9d99 100644 --- a/packages/base/src/panelview/components/layers.tsx +++ b/packages/base/src/panelview/components/layers.tsx @@ -171,6 +171,7 @@ function LayersBodyComponent(props: IBodyProps): JSX.Element { model?.sharedModel.layersChanged.connect(updateLayers); model?.sharedModel.layerTreeChanged.connect(updateLayers); + updateLayers(); return () => { model?.sharedModel.layersChanged.disconnect(updateLayers); model?.sharedModel.layerTreeChanged.disconnect(updateLayers); From ddf3d4837cda34ff5535839d32dfd9d0db70f5c4 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Tue, 22 Apr 2025 21:18:52 -0600 Subject: [PATCH 40/46] Copy docstring raises entries from underlying method docstring --- python/jupytergis_lab/jupytergis_lab/explore.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/explore.py b/python/jupytergis_lab/jupytergis_lab/explore.py index 901b24b0b..4902a4d53 100644 --- a/python/jupytergis_lab/jupytergis_lab/explore.py +++ b/python/jupytergis_lab/jupytergis_lab/explore.py @@ -52,10 +52,10 @@ def explore( :param data: A GeoDataFrame or path to a GeoJSON file. - :raises FileNotFoundException: User passed a file that isn't present. - :raises NotImplementedError: User passed an input value that isn't supported yet. - :raises TypeError: User passed an object type that isn't supported. - :raises ValueError: User passed an object value that isn't supported. + :raises FileNotFoundError: Received a file path that doesn't exist. + :raises NotImplementedError: Received an input value that isn't supported yet. + :raises TypeError: Received an object type that isn't supported. + :raises ValueError: Received an input value that isn't supported. """ doc = GISDocument() From 0b2eab4e9635682a5cd67152e910f0cbaf90eba4 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Thu, 24 Apr 2025 16:30:17 -0600 Subject: [PATCH 41/46] Attempt to fix lite integration tests https://github.com/geojupyter/jupytergis/pull/340#discussion_r2057699027 Co-authored-by: martinRenou Co-authored-by: Nicolas Brichet <32258950+brichet@users.noreply.github.com> --- docs/environment-docs.yml | 1 - docs/user_guide/python_api.md | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/environment-docs.yml b/docs/environment-docs.yml index 7aca293d1..18d61dcf8 100644 --- a/docs/environment-docs.yml +++ b/docs/environment-docs.yml @@ -25,4 +25,3 @@ dependencies: # See: https://github.com/geojupyter/jupytergis/issues/585 - ../python/jupytergis_core - ../python/jupytergis_lab - - ../python/jupytergis diff --git a/docs/user_guide/python_api.md b/docs/user_guide/python_api.md index 0c0e47871..913cc6bf3 100644 --- a/docs/user_guide/python_api.md +++ b/docs/user_guide/python_api.md @@ -37,12 +37,12 @@ Once the document is opened/created, you can start creating GIS layers. ## `explore` ```{eval-rst} -.. autofunction:: jupytergis.explore +.. autofunction:: jupytergis_lab.explore ``` ## `GISDocument` ```{eval-rst} -.. autoclass:: jupytergis.GISDocument +.. autoclass:: jupytergis_lab.GISDocument :members: ``` From 1503af3f2b1b6c3dfb2b3d6e9d93255146105560 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Thu, 24 Apr 2025 16:35:37 -0600 Subject: [PATCH 42/46] Add note about CSS eventually added in JupyterLab Co-authored-by: Nicolas Brichet <32258950+brichet@users.noreply.github.com> --- python/jupytergis_lab/style/base.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/jupytergis_lab/style/base.css b/python/jupytergis_lab/style/base.css index 0e55ed114..637e753c2 100644 --- a/python/jupytergis_lab/style/base.css +++ b/python/jupytergis_lab/style/base.css @@ -420,6 +420,9 @@ div.jGIS-toolbar-widget > div.jp-Toolbar-item:last-child { /* Support output views, created with "Create new view for cell output" context menu */ .jp-LinkedOutputView .jp-OutputArea-child:only-child { + /* NOTE: This will eventually be supported directly in JupyterLab + * (https://github.com/jupyterlab/jupyterlab/pull/17487), but we should keep + * it around for a while for backwards compatibility. */ height: 100%; } .jp-LinkedOutputView .jupytergis-notebook-widget { From 595dfa37077fdc54cf63194640dd27f2e9c684a7 Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Thu, 24 Apr 2025 16:50:53 -0600 Subject: [PATCH 43/46] Move explore module into notebook subpackage Co-authored-by: Nicolas Brichet <32258950+brichet@users.noreply.github.com> --- python/jupytergis_lab/jupytergis_lab/__init__.py | 2 +- python/jupytergis_lab/jupytergis_lab/{ => notebook}/explore.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename python/jupytergis_lab/jupytergis_lab/{ => notebook}/explore.py (100%) diff --git a/python/jupytergis_lab/jupytergis_lab/__init__.py b/python/jupytergis_lab/jupytergis_lab/__init__.py index 1f9d6d96b..8addf6db5 100644 --- a/python/jupytergis_lab/jupytergis_lab/__init__.py +++ b/python/jupytergis_lab/jupytergis_lab/__init__.py @@ -9,7 +9,7 @@ __version__ = "dev" from .notebook import GISDocument # noqa -from .explore import explore +from .notebook.explore import explore def _jupyter_labextension_paths(): diff --git a/python/jupytergis_lab/jupytergis_lab/explore.py b/python/jupytergis_lab/jupytergis_lab/notebook/explore.py similarity index 100% rename from python/jupytergis_lab/jupytergis_lab/explore.py rename to python/jupytergis_lab/jupytergis_lab/notebook/explore.py From 119c281c684d85192ffe5ff65fc1dc4f1ede394b Mon Sep 17 00:00:00 2001 From: Matt Fisher Date: Thu, 24 Apr 2025 17:12:55 -0600 Subject: [PATCH 44/46] Make generic add_layer method a private internal function Co-authored-by: martinRenou Co-authored-by: Nicolas Brichet <32258950+brichet@users.noreply.github.com> --- .../jupytergis_lab/notebook/explore.py | 54 ++++++++++++++++++- .../jupytergis_lab/notebook/gis_document.py | 54 ------------------- 2 files changed, 53 insertions(+), 55 deletions(-) diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/explore.py b/python/jupytergis_lab/jupytergis_lab/notebook/explore.py index 4902a4d53..c20aad040 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/explore.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/explore.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from pathlib import Path from typing import Any, Literal, Optional +import re from jupytergis_lab import GISDocument @@ -62,7 +63,7 @@ def explore( for basemap_obj in _basemaps[basemap]: doc.add_raster_layer(basemap_obj.url, name=basemap_obj.name) - doc.add_layer(data, name=layer_name) + _add_layer(doc=doc, data=data, name=layer_name) # TODO: Zoom to layer. Currently not exposed in Python API. @@ -73,3 +74,54 @@ def explore( # sidecar for the same widget. The user would need to append a semicolon to disable # that behavior. We can't disable that behavior from within this function to the # best of my knowlwedge. + + +def _add_layer( + *, + doc: GISDocument, + data: Any, + name: str, +) -> str: + """Add a layer to the document, autodetecting its type. + + This method currently supports only GeoDataFrames and GeoJSON files. + + :param doc: A GISDocument to add the layer to. + :param data: A data object. Valid data objects include geopandas GeoDataFrames and paths to GeoJSON files. + :param name: The name that will be used for the layer. + + :return: A layer ID string. + + :raises FileNotFoundError: Received a file path that doesn't exist. + :raises NotImplementedError: Received an input value that isn't supported yet. + :raises TypeError: Received an object type that isn't supported. + :raises ValueError: Received an input value that isn't supported. + """ + if isinstance(data, str): + if re.match(r"^(http|https)://", data) is not None: + raise NotImplementedError("URLs not yet supported.") + else: + data = Path(data) + + if isinstance(data, Path): + if not data.exists(): + raise FileNotFoundError(f"File not found: {data}") + + ext = data.suffix.lower() + + if ext in [".geojson", ".json"]: + return doc.add_geojson_layer(path=data, name=name) + elif ext in [".tif", ".tiff"]: + raise NotImplementedError("GeoTIFFs not yet supported.") + else: + raise ValueError(f"Unsupported file type: {data}") + + try: + from geopandas import GeoDataFrame + + if isinstance(data, GeoDataFrame): + return doc.add_geojson_layer(data=data.to_geo_dict(), name=name) + except ImportError: + pass + + raise TypeError(f"Unsupported input type: {type(data)}") diff --git a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py index bae34f5db..3266d58d9 100644 --- a/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py +++ b/python/jupytergis_lab/jupytergis_lab/notebook/gis_document.py @@ -3,7 +3,6 @@ import json import logging import os -import re from pathlib import Path from typing import Any, Dict, List, Literal, Optional, Union from uuid import uuid4 @@ -143,59 +142,6 @@ def export_to_qgis(self, path: str | Path) -> bool: return export_project_to_qgis(path, virtual_file) - def add_layer( - self, - data: Any, - *, - name: str, - attribution: Optional[str] = None, - opacity: float = 1, - ) -> str: - """Add a layer to the document, autodetecting its type. - - This method currently supports only GeoDataFrames and GeoJSON files. - - :param data: A data object. Valid data objects include geopandas GeoDataFrames and paths to GeoJSON files. - :param name: The name that will be used for the layer. - :param attribution: The attribution. - :param opacity: The opacity, between 0 and 1. - - :return: A layer ID string. - - :raises FileNotFoundError: Received a file path that doesn't exist. - :raises NotImplementedError: Received an input value that isn't supported yet. - :raises TypeError: Received an object type that isn't supported. - :raises ValueError: Received an input value that isn't supported. - """ - if isinstance(data, str): - if re.match(r"^(http|https)://", data) is not None: - raise NotImplementedError("URLs not yet supported.") - else: - data = Path(data) - - if isinstance(data, Path): - if not data.exists(): - raise FileNotFoundError(f"File not found: {data}") - - ext = data.suffix.lower() - - if ext in [".geojson", ".json"]: - return self.add_geojson_layer(path=data, name=name) - elif ext in [".tif", ".tiff"]: - raise NotImplementedError("GeoTIFFs not yet supported.") - else: - raise ValueError(f"Unsupported file type: {data}") - - try: - from geopandas import GeoDataFrame - - if isinstance(data, GeoDataFrame): - return self.add_geojson_layer(data=data.to_geo_dict(), name=name) - except ImportError: - pass - - raise TypeError(f"Unsupported input type: {type(data)}") - def add_raster_layer( self, url: str, From 56bc8bdadd60295e98b1256352472123ccaa290e Mon Sep 17 00:00:00 2001 From: martinRenou Date: Fri, 25 Apr 2025 08:38:22 +0200 Subject: [PATCH 45/46] Missing sidecar and export --- lite/environment.yml | 1 + python/jupytergis_lite/jupytergis/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lite/environment.yml b/lite/environment.yml index 55e62479f..db2d857b4 100644 --- a/lite/environment.yml +++ b/lite/environment.yml @@ -11,6 +11,7 @@ dependencies: - ypywidgets>=0.9.6,<0.10.0 - comm>=0.1.2,<0.2.0 - pydantic>=2,<3 + - sidecar - pip: - yjs-widgets>=0.4,<0.5 - my-jupyter-shared-drive<0.2.0 diff --git a/python/jupytergis_lite/jupytergis/__init__.py b/python/jupytergis_lite/jupytergis/__init__.py index 3097075a4..139b9e470 100644 --- a/python/jupytergis_lite/jupytergis/__init__.py +++ b/python/jupytergis_lite/jupytergis/__init__.py @@ -1,3 +1,3 @@ __version__ = "0.4.4" -from jupytergis_lab import GISDocument # noqa +from jupytergis_lab import GISDocument, explore # noqa From 9a909e45b00d3df162143d2e29d159dc14afdaf9 Mon Sep 17 00:00:00 2001 From: martinRenou Date: Fri, 25 Apr 2025 09:25:07 +0200 Subject: [PATCH 46/46] Missing sidecar in docs --- docs/environment.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/environment.yml b/docs/environment.yml index 0320a4c93..7d3f049bb 100644 --- a/docs/environment.yml +++ b/docs/environment.yml @@ -11,6 +11,7 @@ dependencies: - ypywidgets>=0.9.6,<0.10.0 - comm>=0.1.2,<0.2.0 - pydantic>=2,<3 + - sidecar - pip: - yjs-widgets>=0.4,<0.5 - my-jupyter-shared-drive<0.2.0