diff -Nru python-geopandas-0.7.0/appveyor.yml python-geopandas-0.8.1/appveyor.yml --- python-geopandas-0.7.0/appveyor.yml 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/appveyor.yml 2020-07-15 17:54:36.000000000 +0000 @@ -35,9 +35,8 @@ # This PATH modification only works with conda 4.6+, but it won't hurt other versions. - set "PATH=%MINICONDA%\condabin:%PATH%" - conda info -a - # free channel needed for older envs (< py37), see - # https://github.com/conda/conda/issues/8849 - - conda config --set restore_free_channel true + - conda config --add channels conda-forge + - conda config --set channel_priority strict - conda env create --file="${ENV_FILE}" test_script: diff -Nru python-geopandas-0.7.0/asv.conf.json python-geopandas-0.8.1/asv.conf.json --- python-geopandas-0.7.0/asv.conf.json 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/asv.conf.json 2020-07-15 17:54:36.000000000 +0000 @@ -30,6 +30,7 @@ // determined by looking for tools on the PATH environment // variable. "environment_type": "conda", + "conda_channels": ["conda-forge", "defaults"], // timeout in seconds for installing any dependencies in environment // defaults to 10 min @@ -55,7 +56,7 @@ "matrix": { "pandas": [], "shapely": [], - "cython": [], + "pygeos": [], "fiona": [], "pyproj": [], "rtree": [], diff -Nru python-geopandas-0.7.0/benchmarks/clip.py python-geopandas-0.8.1/benchmarks/clip.py --- python-geopandas-0.7.0/benchmarks/clip.py 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/benchmarks/clip.py 2020-07-15 17:54:36.000000000 +0000 @@ -0,0 +1,14 @@ +from geopandas import read_file, datasets, clip +from shapely.geometry import box + + +class Bench: + def setup(self, *args): + world = read_file(datasets.get_path("naturalearth_lowres")) + capitals = read_file(datasets.get_path("naturalearth_cities")) + self.bounds = [box(*geom.bounds) for geom in world.geometry] + self.points = capitals + + def time_clip(self): + for bound in self.bounds: + clip(self.points, bound) diff -Nru python-geopandas-0.7.0/benchmarks/io.py python-geopandas-0.8.1/benchmarks/io.py --- python-geopandas-0.7.0/benchmarks/io.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/benchmarks/io.py 2020-07-15 17:54:36.000000000 +0000 @@ -1,59 +1,109 @@ import os import shutil import tempfile +import warnings -from geopandas import GeoDataFrame, GeoSeries import numpy as np + from shapely.geometry import Point +from geopandas import GeoDataFrame, GeoSeries, read_file, read_parquet, read_feather + + +# TEMP: hide warning from to_parquet +warnings.filterwarnings("ignore", message=".*initial implementation of Parquet.*") + + +format_dict = { + "ESRI Shapefile": ( + ".shp", + lambda gdf, filename: gdf.to_file(filename, driver="ESRI Shapefile"), + lambda filename: read_file(filename, driver="ESRI Shapefile"), + ), + "GeoJSON": ( + ".json", + lambda gdf, filename: gdf.to_file(filename, driver="GeoJSON"), + lambda filename: read_file(filename, driver="GeoJSON"), + ), + "GPKG": ( + ".gpkg", + lambda gdf, filename: gdf.to_file(filename, driver="GeoJSON"), + lambda filename: read_file(filename, driver="GeoJSON"), + ), + "Parquet": ( + ".parquet", + lambda gdf, filename: gdf.to_parquet(filename), + lambda filename: read_parquet(filename), + ), + "Feather": ( + ".feather", + lambda gdf, filename: gdf.to_feather(filename), + lambda filename: read_feather(filename), + ), +} + class Bench: - # extensions for different file types to test - params = [".shp", ".json", ".gpkg"] - param_names = ["ext"] - - def setup(self, ext): - - self.driver_dict = {".shp": "ESRI Shapefile", - ".json": "GeoJSON", - ".gpkg": "GPKG"} - driver = self.driver_dict[ext] + params = ["ESRI Shapefile", "GeoJSON", "GPKG", "Parquet", "Feather"] + param_names = ["file_format"] + + def setup(self, file_format): + self.ext, self.writer, self.reader = format_dict[file_format] num_points = 20000 xs = np.random.rand(num_points) ys = np.random.rand(num_points) self.points = GeoSeries([Point(x, y) for (x, y) in zip(xs, ys)]) - self.df = GeoDataFrame({"geometry": self.points, "x": xs, "y": ys, - "s": np.zeros(num_points, dtype="object")}) + self.df = GeoDataFrame( + { + "geometry": self.points, + "x": xs, + "y": ys, + "s": np.zeros(num_points, dtype="object"), + } + ) self.tmpdir = tempfile.mkdtemp() - self.series_filename = os.path.join(self.tmpdir, "series" + ext) - self.frame_filename = os.path.join(self.tmpdir, "frame" + ext) - self.points.to_file(self.series_filename, driver=driver) - self.df.to_file(self.frame_filename, driver=driver) + self.filename = os.path.join(self.tmpdir, "frame" + self.ext) + self.writer(self.df, self.filename) - def teardown(self, ext): + def teardown(self, file_format): shutil.rmtree(self.tmpdir) - def time_write_frame(self, ext): - driver = self.driver_dict[ext] - with tempfile.TemporaryDirectory() as tmpdir: - out_filename = os.path.join(tmpdir, "frame" + ext) - self.df.to_file(out_filename, driver=driver) - def time_write_series(self, ext): - driver = self.driver_dict[ext] +class BenchFrame(Bench): + + params = ["ESRI Shapefile", "GeoJSON", "GPKG", "Parquet", "Feather"] + param_names = ["file_format"] + + def time_write(self, file_format): with tempfile.TemporaryDirectory() as tmpdir: - out_filename = os.path.join(tmpdir, "series" + ext) - self.points.to_file(out_filename, driver=driver) + out_filename = os.path.join(tmpdir, "frame" + self.ext) + self.writer(self.df, out_filename) + + def time_read(self, file_format): + self.reader(self.filename) + - def time_read_frame(self, ext): - frame = GeoDataFrame.from_file(self.frame_filename) +class BenchSeries(Bench): + + params = ["ESRI Shapefile", "GeoJSON", "GPKG"] + param_names = ["file_format"] + + def setup(self, file_format): + super().setup(file_format) + self.filename_series = os.path.join(self.tmpdir, "series" + self.ext) + self.writer(self.points, self.filename_series) + + def time_write_series(self, file_format): + with tempfile.TemporaryDirectory() as tmpdir: + out_filename = os.path.join(tmpdir, "series" + self.ext) + self.writer(self.points, out_filename) - def time_read_series(self, ext): - points = GeoSeries.from_file(self.series_filename) + def time_read_series(self, file_format): + GeoSeries.from_file(self.filename_series) - def time_read_series_from_frame(self, ext): - points = GeoSeries.from_file(self.frame_filename) + def time_read_series_from_frame(self, file_format): + GeoSeries.from_file(self.filename) diff -Nru python-geopandas-0.7.0/benchmarks/sindex.py python-geopandas-0.8.1/benchmarks/sindex.py --- python-geopandas-0.7.0/benchmarks/sindex.py 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/benchmarks/sindex.py 2020-07-15 17:54:36.000000000 +0000 @@ -0,0 +1,69 @@ +from geopandas import read_file, datasets +from geopandas.sindex import VALID_QUERY_PREDICATES + + +def generate_test_df(): + world = read_file(datasets.get_path("naturalearth_lowres")) + capitals = read_file(datasets.get_path("naturalearth_cities")) + countries = world.to_crs("epsg:3395")[["geometry"]] + capitals = capitals.to_crs("epsg:3395")[["geometry"]] + mixed = capitals.append(countries) # get a mix of geometries + points = capitals + polygons = countries + # filter out invalid geometries + data = { + "mixed": mixed[mixed.is_valid], + "points": points[points.is_valid], + "polygons": polygons[polygons.is_valid], + } + return data + + +class Bench: + + param_names = ["tree_geom_type"] + params = [["mixed", "points", "polygons"]] + + def setup(self, *args): + self.data = generate_test_df() + # cache bounds so that bound creation is not counted in benchmarks + self.bounds = [g.bounds for g in self.data["mixed"].geometry] + + def time_index_creation(self, tree_geom_type): + """Time creation of spatial index. + + Note: pygeos will only create the index once; this benchmark + is not intended to be used to compare rtree and pygeos. + """ + self.data[tree_geom_type]._invalidate_sindex() + self.data[tree_geom_type]._generate_sindex() + + def time_intersects(self, tree_geom_type): + for bounds in self.bounds: + self.data[tree_geom_type].sindex.intersection(bounds) + + def time_intersects_objects(self, tree_geom_type): + for bounds in self.bounds: + self.data[tree_geom_type].sindex.intersection(bounds, objects=True) + + +class BenchQuery: + + param_names = ["predicate", "input_geom_type", "tree_geom_type"] + params = [ + [*VALID_QUERY_PREDICATES], + ["mixed", "points", "polygons"], + ["mixed", "points", "polygons"], + ] + + def setup(self, *args): + self.data = generate_test_df() + + def time_query_bulk(self, predicate, input_geom_type, tree_geom_type): + self.data[tree_geom_type].sindex.query_bulk( + self.data[input_geom_type].geometry, predicate=predicate + ) + + def time_query(self, predicate, input_geom_type, tree_geom_type): + for geo in self.data[input_geom_type].geometry.sample(10, random_state=0): + self.data[tree_geom_type].sindex.query(geo, predicate=predicate) diff -Nru python-geopandas-0.7.0/CHANGELOG.md python-geopandas-0.8.1/CHANGELOG.md --- python-geopandas-0.7.0/CHANGELOG.md 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/CHANGELOG.md 2020-07-15 17:54:36.000000000 +0000 @@ -1,5 +1,111 @@ -Changes -======= +Changelog +========= + + +Version 0.8.1 (July 15, 2020) +----------------------------- + +Small bug-fix release: + +- Fix a regression in the `plot()` method when visualizing with a + JenksCaspallSampled or FisherJenksSampled scheme (#1486). +- Fix spurious warning in `GeoDataFrame.to_postgis` (#1497). +- Fix the un-pickling with `pd.read_pickle` of files written with older + GeoPandas versions (#1511). + + +Version 0.8.0 (June 24, 2020) +----------------------------- + +**Experimental**: optional use of PyGEOS to speed up spatial operations (#1155). +PyGEOS is a faster alternative for Shapely (being contributed back to a future +version of Shapely), and is used in element-wise spatial operations and for +spatial index in e.g. `sjoin` (#1343, #1401, #1421, #1427, #1428). See the +[installation docs](https://geopandas.readthedocs.io/en/latest/install.html#using-the-optional-pygeos-dependency) +for more info and how to enable it. + +New features and improvements: + +- IO enhancements: + - New `GeoDataFrame.to_postgis()` method to write to PostGIS database (#1248). + - New Apache Parquet and Feather file format support (#1180, #1435) + - Allow appending to files with `GeoDataFrame.to_file` (#1229). + - Add support for the `ignore_geometry` keyword in `read_file` to only read + the attribute data. If set to True, a pandas DataFrame without geometry is + returned (#1383). + - `geopandas.read_file` now supports reading from file-like objects (#1329). + - `GeoDataFrame.to_file` now supports specifying the CRS to write to the file + (#802). By default it still uses the CRS of the GeoDataFrame. + - New `chunksize` keyword in `geopandas.read_postgis` to read a query in + chunks (#1123). +- Improvements related to geometry columns and CRS: + - Any column of the GeoDataFrame that has a "geometry" dtype is now returned + as a GeoSeries. This means that when having multiple geometry columns, not + only the "active" geometry column is returned as a GeoSeries, but also + accessing another geometry column (`gdf["other_geom_column"]`) gives a + GeoSeries (#1336). + - Multiple geometry columns in a GeoDataFrame can now each have a different + CRS. The global `gdf.crs` attribute continues to returns the CRS of the + "active" geometry column. The CRS of other geometry columns can be accessed + from the column itself (eg `gdf["other_geom_column"].crs`) (#1339). + - New `set_crs()` method on GeoDataFrame/GeoSeries to set the CRS of naive + geometries (#747). +- Improvements related to plotting: + - The y-axis is now scaled depending on the center of the plot when using a + geographic CRS, instead of using an equal aspect ratio (#1290). + - When passing a column of categorical dtype to the `column=` keyword of the + GeoDataFrame `plot()`, we now honor all categories and its order (#1483). + In addition, a new `categories` keyword allows to specify all categories + and their order otherwise (#1173). + - For choropleths using a classification scheme (using `scheme=`), the + `legend_kwds` accept two new keywords to control the formatting of the + legend: `fmt` with a format string for the bin edges (#1253), and `labels` + to pass fully custom class labels (#1302). +- New `covers()` and `covered_by()` methods on GeoSeries/GeoDataframe for the + equivalent spatial predicates (#1460, #1462). +- GeoPandas now warns when using distance-based methods with data in a + geographic projection (#1378). + +Deprecations: + +- When constructing a GeoSeries or GeoDataFrame from data that already has a + CRS, a deprecation warning is raised when both CRS don't match, and in the + future an error will be raised in such a case. You can use the new `set_crs` + method to override an existing CRS. See + [the docs](https://geopandas.readthedocs.io/en/latest/projections.html#projection-for-multiple-geometry-columns). +- The helper functions in the `geopandas.plotting` module are deprecated for + public usage (#656). +- The `geopandas.io` functions are deprecated, use the top-level `read_file` and + `to_file` instead (#1407). +- The set operators (`&`, `|`, `^`, `-`) are deprecated, use the + `intersection()`, `union()`, `symmetric_difference()`, `difference()` methods + instead (#1255). +- The `sindex` for empty dataframe will in the future return an empty spatial + index instead of `None` (#1438). +- The `objects` keyword in the `intersection` method of the spatial index + returned by the `sindex` attribute is deprecated and will be removed in the + future (#1440). + +Bug fixes: + +- Fix the `total_bounds()` method to ignore missing and empty geometries (#1312). +- Fix `geopandas.clip` when masking with non-overlapping area resulting in an + empty GeoDataFrame (#1309, #1365). +- Fix error in `geopandas.sjoin` when joining on an empty geometry column (#1318). +- CRS related fixes: `pandas.concat` preserves CRS when concatenating GeoSeries + objects (#1340), preserve the CRS in `geopandas.clip` (#1362) and in + `GeoDataFrame.astype` (#1366). +- Fix bug in `GeoDataFrame.explode()` when 'level_1' is one of the column names + (#1445). +- Better error message when rtree is not installed (#1425). +- Fix bug in `GeoSeries.equals()` (#1451). +- Fix plotting of multi-part geometries with additional style keywords (#1385). + +And we now have a [Code of Conduct](https://github.com/geopandas/geopandas/blob/master/CODE_OF_CONDUCT.md)! + +GeoPandas 0.8.0 is the last release to support Python 3.5. The next release +will require Python 3.6, pandas 0.24, numpy 1.15 and shapely 1.6 or higher. + Version 0.7.0 (February 16, 2020) --------------------------------- diff -Nru python-geopandas-0.7.0/ci/travis/35-minimal.yaml python-geopandas-0.8.1/ci/travis/35-minimal.yaml --- python-geopandas-0.7.0/ci/travis/35-minimal.yaml 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/ci/travis/35-minimal.yaml 2020-07-15 17:54:36.000000000 +0000 @@ -19,10 +19,10 @@ - matplotlib - descartes - matplotlib=2.0 - - mapclassify + - mapclassify>=2.2.0 - geopy - SQLalchemy - - psycopg2 - libspatialite + - pyarrow - pip: - pyproj==2.2.2 diff -Nru python-geopandas-0.7.0/ci/travis/36-pd023.yaml python-geopandas-0.8.1/ci/travis/36-pd023.yaml --- python-geopandas-0.7.0/ci/travis/36-pd023.yaml 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/ci/travis/36-pd023.yaml 2020-07-15 17:54:36.000000000 +0000 @@ -11,6 +11,7 @@ - gdal=2.3 - fiona #- pyproj + - geos # testing - pytest - pytest-cov @@ -19,12 +20,12 @@ - rtree - matplotlib=2 - descartes - - mapclassify #- geopy - SQLalchemy - - psycopg2 - libspatialite - pip: - pyproj==2.3.1 - geopy - codecov + - mapclassify>=2.2.0 + - git+https://github.com/pygeos/pygeos.git diff -Nru python-geopandas-0.7.0/ci/travis/36-pd024.yaml python-geopandas-0.8.1/ci/travis/36-pd024.yaml --- python-geopandas-0.7.0/ci/travis/36-pd024.yaml 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/ci/travis/36-pd024.yaml 2020-07-15 17:54:36.000000000 +0000 @@ -8,6 +8,7 @@ - shapely - fiona=1.7 #- pyproj + - geos # testing - pytest - pytest-cov @@ -18,10 +19,11 @@ - descartes #- geopy - SQLalchemy - - psycopg2 - libspatialite + - pyarrow - pip: - pyproj - codecov - geopy - - mapclassify + - mapclassify>=2.2.0 + - git+https://github.com/pygeos/pygeos.git diff -Nru python-geopandas-0.7.0/ci/travis/37-dev.yaml python-geopandas-0.8.1/ci/travis/37-dev.yaml --- python-geopandas-0.7.0/ci/travis/37-dev.yaml 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/ci/travis/37-dev.yaml 2020-07-15 17:54:36.000000000 +0000 @@ -9,6 +9,7 @@ - shapely - fiona - pyproj + - geos # testing - pytest - pytest-cov @@ -19,9 +20,10 @@ - descartes #- geopy - SQLalchemy - - psycopg2 - libspatialite + - pyarrow - pip: - codecov - geopy - - mapclassify + - mapclassify>=2.2.0 + - git+https://github.com/pygeos/pygeos.git diff -Nru python-geopandas-0.7.0/ci/travis/37-latest-conda-forge.yaml python-geopandas-0.8.1/ci/travis/37-latest-conda-forge.yaml --- python-geopandas-0.7.0/ci/travis/37-latest-conda-forge.yaml 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/ci/travis/37-latest-conda-forge.yaml 2020-07-15 17:54:36.000000000 +0000 @@ -8,6 +8,7 @@ - shapely - fiona - pyproj + - pygeos # testing - pytest - pytest-cov @@ -16,8 +17,8 @@ - rtree - matplotlib - descartes - - pysal + - mapclassify - geopy - SQLalchemy - - psycopg2 - libspatialite + - pyarrow diff -Nru python-geopandas-0.7.0/ci/travis/37-latest-defaults.yaml python-geopandas-0.8.1/ci/travis/37-latest-defaults.yaml --- python-geopandas-0.7.0/ci/travis/37-latest-defaults.yaml 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/ci/travis/37-latest-defaults.yaml 2020-07-15 17:54:36.000000000 +0000 @@ -8,6 +8,7 @@ - shapely - fiona - pyproj + - geos # testing - pytest - pytest-cov @@ -18,9 +19,10 @@ - descartes #- geopy - SQLalchemy - - psycopg2 - libspatialite + - pyarrow - pip: - codecov - geopy - mapclassify + - git+https://github.com/pygeos/pygeos.git diff -Nru python-geopandas-0.7.0/ci/travis/38-latest-conda-forge.yaml python-geopandas-0.8.1/ci/travis/38-latest-conda-forge.yaml --- python-geopandas-0.7.0/ci/travis/38-latest-conda-forge.yaml 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/ci/travis/38-latest-conda-forge.yaml 2020-07-15 17:54:36.000000000 +0000 @@ -8,6 +8,7 @@ - shapely - fiona - pyproj + - pygeos # testing - pytest - pytest-cov @@ -16,8 +17,12 @@ - rtree - matplotlib - descartes - - pysal + - mapclassify - geopy + # installed in travis.yml, because not available on windows + # - postgis - SQLalchemy - psycopg2 - libspatialite + - geoalchemy2 + - pyarrow diff -Nru python-geopandas-0.7.0/ci/travis/38-no-optional-deps.yaml python-geopandas-0.8.1/ci/travis/38-no-optional-deps.yaml --- python-geopandas-0.7.0/ci/travis/38-no-optional-deps.yaml 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/ci/travis/38-no-optional-deps.yaml 2020-07-15 17:54:36.000000000 +0000 @@ -0,0 +1,14 @@ +name: test +channels: + - conda-forge +dependencies: + - python=3.8 + # required + - pandas + - shapely + - fiona + - pyproj + # testing + - pytest + - pytest-cov + - codecov diff -Nru python-geopandas-0.7.0/ci/travis/setup_postgres.sh python-geopandas-0.8.1/ci/travis/setup_postgres.sh --- python-geopandas-0.7.0/ci/travis/setup_postgres.sh 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/ci/travis/setup_postgres.sh 2020-07-15 17:54:36.000000000 +0000 @@ -0,0 +1,21 @@ +#!/bin/bash -e + +echo "Setting up Postgresql" + +mkdir -p ${HOME}/var +rm -rf ${HOME}/var/db + +pg_ctl initdb -D ${HOME}/var/db +pg_ctl start -D ${HOME}/var/db + +echo -n 'waiting for postgres' +while [ ! -e /tmp/.s.PGSQL.5432 ]; do + sleep 1 + echo -n '.' +done + +createuser -U travis -s postgres +createdb --owner=postgres test_geopandas +psql -d test_geopandas -q -c "CREATE EXTENSION postgis" + +echo "Done setting up Postgresql" diff -Nru python-geopandas-0.7.0/CODE_OF_CONDUCT.md python-geopandas-0.8.1/CODE_OF_CONDUCT.md --- python-geopandas-0.7.0/CODE_OF_CONDUCT.md 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/CODE_OF_CONDUCT.md 2020-07-15 17:54:36.000000000 +0000 @@ -0,0 +1,133 @@ +# GeoPandas Project Code of Conduct + +Behind the GeoPandas Project is an engaged and respectful community made up of people +from all over the world and with a wide range of backgrounds. +Naturally, this implies diversity of ideas and perspectives on often complex +problems. Disagreement and healthy discussion of conflicting viewpoints is +welcome: the best solutions to hard problems rarely come from a single angle. +But disagreement is not an excuse for aggression: humans tend to take +disagreement personally and easily drift into behavior that ultimately degrades +a community. This is particularly acute with online communication across +language and cultural gaps, where many cues of human behavior are unavailable. +We are outlining here a set of principles and processes to support a +healthy community in the face of these challenges. + +Fundamentally, we are committed to fostering a productive, harassment-free +environment for everyone. Rather than considering this code an exhaustive list +of things that you can’t do, take it in the spirit it is intended - a guide to +make it easier to enrich all of us and the communities in which we participate. + +Importantly: as a member of our community, *you are also a steward of these +values*. Not all problems need to be resolved via formal processes, and often +a quick, friendly but clear word on an online forum or in person can help +resolve a misunderstanding and de-escalate things. + +However, sometimes these informal processes may be inadequate: they fail to +work, there is urgency or risk to someone, nobody is intervening publicly and +you don't feel comfortable speaking in public, etc. For these or other +reasons, structured follow-up may be necessary and here we provide the means +for that: we welcome reports by emailing +[*geopandas-conduct@googlegroups.com*](mailto:geopandas-conduct@googlegroups.com) +or by filling out +[this form](https://docs.google.com/forms/d/e/1FAIpQLSd8Tbi2zNl1i2N9COX0yavHEqTGFIPQ1_cLcy1A3JgVc1OrAQ/viewform). + +This code applies equally to founders, developers, mentors and new community +members, in all spaces managed by the GeoPandas Project. This +includes the mailing lists, our GitHub organization, our chat room, in-person +events, and any other forums created by the project team. In addition, +violations of this code outside these spaces may affect a person's ability to +participate within them. + +By embracing the following principles, guidelines and actions to follow or +avoid, you will help us make the GeoPandas Project a welcoming and productive community. Feel +free to contact the Code of Conduct Committee at +[*geopandas-conduct@googlegroups.com*](mailto:geopandas-conduct@googlegroups.com) with any questions. + + +1. **Be friendly and patient**. + +2. **Be welcoming**. We strive to be a community that welcomes and supports + people of all backgrounds and identities. This includes, but is not limited + to, members of any race, ethnicity, culture, national origin, color, + immigration status, social and economic class, educational level, sex, sexual + orientation, gender identity and expression, age, physical appearance, family + status, technological or professional choices, academic + discipline, religion, mental ability, and physical ability. + +3. **Be considerate**. Your work will be used by other people, and you in turn + will depend on the work of others. Any decision you take will affect users + and colleagues, and you should take those consequences into account when + making decisions. Remember that we're a world-wide community. You may be + communicating with someone with a different primary language or cultural + background. + +4. **Be respectful**. Not all of us will agree all the time, but disagreement is + no excuse for poor behavior or poor manners. We might all experience some + frustration now and then, but we cannot allow that frustration to turn into a + personal attack. It’s important to remember that a community where people + feel uncomfortable or threatened is not a productive one. + +5. **Be careful in the words that you choose**. Be kind to others. Do not insult + or put down other community members. Harassment and other exclusionary + behavior are not acceptable. This includes, but is not limited to: + * Violent threats or violent language directed against another person + * Discriminatory jokes and language + * Posting sexually explicit or violent material + * Posting (or threatening to post) other people's personally identifying + information ("doxing") + * Personal insults, especially those using racist, sexist, and xenophobic terms + * Unwelcome sexual attention + * Advocating for, or encouraging, any of the above behavior + * Repeated harassment of others. In general, if someone asks you to stop, + then stop + +6. **Moderate your expectations**. Please respect that community members choose + how they spend their time in the project. A thoughtful question about your + expectations is preferable to demands for another person's time. + +7. **When we disagree, try to understand why**. Disagreements, both social and + technical, happen all the time and the GeoPandas Project is no exception. Try to + understand where others are coming from, as seeing a question from their + viewpoint may help find a new path forward. And don’t forget that it is + human to err: blaming each other doesn’t get us anywhere, while we can learn + from mistakes to find better solutions. + +8. **A simple apology can go a long way**. It can often de-escalate a situation, + and telling someone that you are sorry is an act of empathy that doesn’t + automatically imply an admission of guilt. + + +## Reporting + +If you believe someone is violating the code of conduct, please report this in +a timely manner. Code of conduct violations reduce the value of the community +for everyone and we take them seriously. + +You can file a report by emailing +[*geopandas-conduct@googlegroups.com*](mailto:geopandas-conduct@googlegroups.com) or by filing out +[this form](https://docs.google.com/forms/d/e/1FAIpQLSd8Tbi2zNl1i2N9COX0yavHEqTGFIPQ1_cLcy1A3JgVc1OrAQ/viewform). + +The online form gives you the option to keep your report anonymous or request +that we follow up with you directly. While we cannot follow up on an anonymous +report, we will take appropriate action. + +Messages sent to the e-mail address or through the form will be sent +only to the Code of Conduct Committee, which currently consists of: + +* Hannah Aizenman +* Joris Van den Bossche +* Martin Fleischmann + + +## Enforcement + +Enforcement procedures within the GeoPandas Project follow Project Jupyter's +[*Enforcement Manual*](https://github.com/jupyter/governance/blob/master/conduct/enforcement.md). For information on enforcement, please view the [original manual](https://github.com/jupyter/governance/blob/master/conduct/enforcement.md). + +Original text courtesy of the [*Speak +Up!*](http://web.archive.org/web/20141109123859/http://speakup.io/coc.html), +[*Django*](https://www.djangoproject.com/conduct) and [*Jupyter*](https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md) Projects, +modified by the GeoPandas Project. We are grateful to those projects for contributing these materials under open licensing terms for us to easily reuse. + +All content on this page is licensed under a [*Creative Commons +Attribution*](http://creativecommons.org/licenses/by/3.0/) license. diff -Nru python-geopandas-0.7.0/CONTRIBUTING.md python-geopandas-0.8.1/CONTRIBUTING.md --- python-geopandas-0.7.0/CONTRIBUTING.md 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/CONTRIBUTING.md 2020-07-15 17:54:36.000000000 +0000 @@ -13,8 +13,13 @@ where applicable. Please read the [pandas contributing guidelines](http://pandas.pydata.org/pandas-docs/stable/contributing.html). + In particular, when submitting a pull request: +- Install the requirements for the development environment (one can do this + with either conda, and the environment.yml file, or pip, and the + requirements-dev.txt file, and can use the pandas contributing guidelines + as a guide). - All existing tests should pass. Please make sure that the test suite passes, both locally and on [Travis CI](https://travis-ci.org/geopandas/geopandas). Status on @@ -46,7 +51,7 @@ code format throughout the project. - Imports should be grouped with standard library imports first, - 3rd-party libraries next, and GeoPandas imports third. Within each + third-party libraries next, and GeoPandas imports third. Within each grouping, imports should be alphabetized. Always use absolute imports when possible, and explicit relative imports for local imports when necessary in tests. diff -Nru python-geopandas-0.7.0/debian/changelog python-geopandas-0.8.1/debian/changelog --- python-geopandas-0.7.0/debian/changelog 2020-02-17 14:17:10.000000000 +0000 +++ python-geopandas-0.8.1/debian/changelog 2020-12-19 10:00:00.000000000 +0000 @@ -1,3 +1,39 @@ +python-geopandas (0.8.1-1~focal2) focal; urgency=medium + + * No change rebuild for GDAL 3.2.0 transition. + + -- Angelos Tzotsos Sat, 19 Dec 2020 12:00:00 +0200 + +python-geopandas (0.8.1-1~focal1) focal; urgency=medium + + * No change rebuild for GDAL 3.1.3 transition. + + -- Angelos Tzotsos Fri, 02 Oct 2020 14:00:00 +0300 + +python-geopandas (0.8.1-1~focal0) focal; urgency=medium + + * No change rebuild for Focal. + + -- Angelos Tzotsos Sat, 08 Aug 2020 20:00:00 +0300 + +python-geopandas (0.8.1-1) unstable; urgency=medium + + * Team upload. + * New upstream release. + + -- Bas Couwenberg Thu, 16 Jul 2020 05:39:56 +0200 + +python-geopandas (0.8.0-1) unstable; urgency=medium + + * Team upload. + * New upstream release. + * Bump debhelper compat to 10, changes: + - Drop --parallel option, enabled by default + * Refresh patches. + * Add lintian override for broken-zip. + + -- Bas Couwenberg Wed, 24 Jun 2020 15:50:39 +0200 + python-geopandas (0.7.0-1) unstable; urgency=medium * Team upload. diff -Nru python-geopandas-0.7.0/debian/compat python-geopandas-0.8.1/debian/compat --- python-geopandas-0.7.0/debian/compat 2014-12-30 07:22:01.000000000 +0000 +++ python-geopandas-0.8.1/debian/compat 2020-03-19 19:28:16.000000000 +0000 @@ -1 +1 @@ -9 +10 diff -Nru python-geopandas-0.7.0/debian/control python-geopandas-0.8.1/debian/control --- python-geopandas-0.7.0/debian/control 2020-02-07 05:45:38.000000000 +0000 +++ python-geopandas-0.8.1/debian/control 2020-03-19 19:28:23.000000000 +0000 @@ -3,7 +3,7 @@ Priority: optional Maintainer: Debian GIS Project Uploaders: Johan Van de Wauw -Build-Depends: debhelper (>= 9), +Build-Depends: debhelper (>= 10~), dh-python, gdal-bin, cython3, diff -Nru python-geopandas-0.7.0/debian/patches/no-gallery.patch python-geopandas-0.8.1/debian/patches/no-gallery.patch --- python-geopandas-0.7.0/debian/patches/no-gallery.patch 2020-02-17 14:16:32.000000000 +0000 +++ python-geopandas-0.8.1/debian/patches/no-gallery.patch 2020-06-24 13:50:11.000000000 +0000 @@ -12,7 +12,7 @@ 'sphinx.ext.autosummary', 'sphinx.ext.intersphinx', 'sphinx.ext.autodoc', -@@ -56,20 +55,6 @@ templates_path = ['_templates'] +@@ -57,18 +56,6 @@ templates_path = ['_templates'] autosummary_generate = True @@ -25,11 +25,9 @@ - 'reference_url': {'matplotlib': 'http://matplotlib.org', - 'numpy': 'http://docs.scipy.org/doc/numpy', - 'scipy': 'http://docs.scipy.org/doc/scipy/reference', -- 'pyproj': 'http://pyproj4.github.io/pyproj/stable/', - 'geopandas': None}, - 'backreferences_dir': 'reference' -} -- + # connect docs in other projects + intersphinx_mapping = {'pyproj': ('http://pyproj4.github.io/pyproj/stable/', None)} # suppress matplotlib warning in examples - warnings.filterwarnings( - "ignore", diff -Nru python-geopandas-0.7.0/debian/python3-geopandas.lintian-overrides python-geopandas-0.8.1/debian/python3-geopandas.lintian-overrides --- python-geopandas-0.7.0/debian/python3-geopandas.lintian-overrides 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/debian/python3-geopandas.lintian-overrides 2020-06-24 14:01:23.000000000 +0000 @@ -0,0 +1,3 @@ +# Zip archive data, at least v2.0 to extract +broken-zip usr/lib/python*/dist-packages/geopandas/datasets/nybb_16a.zip + diff -Nru python-geopandas-0.7.0/debian/rules python-geopandas-0.8.1/debian/rules --- python-geopandas-0.7.0/debian/rules 2019-12-24 04:44:02.000000000 +0000 +++ python-geopandas-0.8.1/debian/rules 2020-06-24 13:59:09.000000000 +0000 @@ -5,7 +5,7 @@ export PYBUILD_NAME=geopandas %: - dh $@ --with python3 --buildsystem pybuild --parallel + dh $@ --with python3 --buildsystem pybuild override_dh_clean: dh_clean diff -Nru python-geopandas-0.7.0/doc/environment.yml python-geopandas-0.8.1/doc/environment.yml --- python-geopandas-0.7.0/doc/environment.yml 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/doc/environment.yml 2020-07-15 17:54:36.000000000 +0000 @@ -15,6 +15,7 @@ - sphinx=2.4.1 - sphinx_rtd_theme=0.4.3 - numpydoc=0.9.2 +- recommonmark==0.6.0 - ipython=7.12.0 - pillow=7.0.0 - mock=3.0.5 diff -Nru python-geopandas-0.7.0/doc/source/changelog.rst python-geopandas-0.8.1/doc/source/changelog.rst --- python-geopandas-0.7.0/doc/source/changelog.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/doc/source/changelog.rst 2020-07-15 17:54:36.000000000 +0000 @@ -0,0 +1 @@ +.. include:: ../../CHANGELOG.md diff -Nru python-geopandas-0.7.0/doc/source/code_of_conduct.rst python-geopandas-0.8.1/doc/source/code_of_conduct.rst --- python-geopandas-0.7.0/doc/source/code_of_conduct.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/doc/source/code_of_conduct.rst 2020-07-15 17:54:36.000000000 +0000 @@ -0,0 +1,135 @@ +GeoPandas Project Code of Conduct +================================= + +Behind the GeoPandas Project is an engaged and respectful community made up of +people from all over the world and with a wide range of backgrounds. +Naturally, this implies diversity of ideas and perspectives on often +complex problems. Disagreement and healthy discussion of conflicting +viewpoints is welcome: the best solutions to hard problems rarely come from a single +angle. But disagreement is not an excuse for aggression: humans tend to take +disagreement personally and easily drift into behavior that ultimately +degrades a community. This is particularly acute with online communication +across language and cultural gaps, where many cues of human behavior are +unavailable. We are outlining here a set of principles and processes to support a +healthy community in the face of these challenges. + +Fundamentally, we are committed to fostering a productive, harassment-free +environment for everyone. Rather than considering this code an exhaustive list +of things that you can’t do, take it in the spirit it is intended - a guide to +make it easier to enrich all of us and the communities in which we participate. + +Importantly: as a member of our community, *you are also a steward of these +values*. Not all problems need to be resolved via formal processes, and often +a quick, friendly but clear word on an online forum or in person can help +resolve a misunderstanding and de-escalate things. + +However, sometimes these informal processes may be inadequate: they fail to +work, there is urgency or risk to someone, nobody is intervening publicly and +you don't feel comfortable speaking in public, etc. For these or other +reasons, structured follow-up may be necessary and here we provide the means +for that: we welcome reports by emailing +`geopandas-conduct@googlegroups.com `__ or by filling out `this +form `__. + +This code applies equally to founders, developers, mentors and new community +members, in all spaces managed by the GeoPandas Project. This +includes the mailing lists, our GitHub organization, our chat room, in-person +events, and any other forums created by the project team. In addition, +violations of this code outside these spaces may affect a person's ability to +participate within them. + +By embracing the following principles, guidelines and actions to follow or +avoid, you will help us make Jupyter a welcoming and productive community. Feel +free to contact the Code of Conduct Committee at +`geopandas-conduct@googlegroups.com `__ with any questions. + +1. **Be friendly and patient**. + +2. **Be welcoming**. We strive to be a community that welcomes and supports + people of all backgrounds and identities. This includes, but is not limited + to, members of any race, ethnicity, culture, national origin, color, + immigration status, social and economic class, educational level, sex, sexual + orientation, gender identity and expression, age, physical appearance, family + status, technological or professional choices, academic + discipline, religion, mental ability, and physical ability. + +3. **Be considerate**. Your work will be used by other people, and you in turn + will depend on the work of others. Any decision you take will affect users + and colleagues, and you should take those consequences into account when + making decisions. Remember that we're a world-wide community. You may be + communicating with someone with a different primary language or cultural + background. + +4. **Be respectful**. Not all of us will agree all the time, but disagreement is + no excuse for poor behavior or poor manners. We might all experience some + frustration now and then, but we cannot allow that frustration to turn into a + personal attack. It’s important to remember that a community where people + feel uncomfortable or threatened is not a productive one. + +5. **Be careful in the words that you choose**. Be kind to others. Do not insult + or put down other community members. Harassment and other exclusionary + behavior are not acceptable. This includes, but is not limited to: + + - Violent threats or violent language directed against another person + - Discriminatory jokes and language + - Posting sexually explicit or violent material + - Posting (or threatening to post) other people's personally identifying information ("doxing") + - Personal insults, especially those using racist, sexist, and xenophobic terms + - Unwelcome sexual attention + - Advocating for, or encouraging, any of the above behavior + - Repeated harassment of others. In general, if someone asks you to stop, then stop + +6. **Moderate your expectations**. Please respect that community members choose + how they spend their time in the project. A thoughtful question about your + expectations is preferable to demands for another person's time. + +7. **When we disagree, try to understand why**. Disagreements, both social and + technical, happen all the time and the GeoPandas Project is no exception. Try to + understand where others are coming from, as seeing a question from their + viewpoint may help find a new path forward. And don’t forget that it is + human to err: blaming each other doesn’t get us anywhere, while we can learn + from mistakes to find better solutions. + +8. **A simple apology can go a long way**. It can often de-escalate a situation, + and telling someone that you are sorry is an act of empathy that doesn’t + automatically imply an admission of guilt. + +Reporting +--------- + +If you believe someone is violating the code of conduct, please report this in +a timely manner. Code of conduct violations reduce the value of the community +for everyone and we take them seriously. + +You can file a report by emailing +`geopandas-conduct@googlegroups.com `__ or by filing out +`this form `__. + +The online form gives you the option to keep your report anonymous or request +that we follow up with you directly. While we cannot follow up on an anonymous +report, we will take appropriate action. + +Messages sent to the e-mail address or through the form will be sent +only to the Code of Conduct Committee, which currently consists of: + +- Hannah Aizenman +- Joris Van den Bossche +- Martin Fleischmann + +Enforcement +----------- + +Enforcement procedures within the GeoPandas Project follow Project Jupyter's `Enforcement +Manual `__. +For information on enforcement, please view the `original +manual `__. + +Original text courtesy of the `Speak +Up! `__, +`Django `__ and +`Jupyter `__ +Projects, modified by the GeoPandas Project. We are grateful to those projects for +contributing these materials under open licensing terms for us to easily reuse. + +All content on this page is licensed under a `Creative Commons +Attribution `__ license. diff -Nru python-geopandas-0.7.0/doc/source/conf.py python-geopandas-0.8.1/doc/source/conf.py --- python-geopandas-0.7.0/doc/source/conf.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/doc/source/conf.py 2020-07-15 17:54:36.000000000 +0000 @@ -32,6 +32,7 @@ 'sphinx.ext.autosummary', 'sphinx.ext.intersphinx', 'sphinx.ext.autodoc', + 'recommonmark', 'numpydoc', ] @@ -65,11 +66,11 @@ 'reference_url': {'matplotlib': 'http://matplotlib.org', 'numpy': 'http://docs.scipy.org/doc/numpy', 'scipy': 'http://docs.scipy.org/doc/scipy/reference', - 'pyproj': 'http://pyproj4.github.io/pyproj/stable/', 'geopandas': None}, 'backreferences_dir': 'reference' } - +# connect docs in other projects +intersphinx_mapping = {'pyproj': ('http://pyproj4.github.io/pyproj/stable/', None)} # suppress matplotlib warning in examples warnings.filterwarnings( "ignore", @@ -79,7 +80,7 @@ ) # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ['.rst', '.md'] # The encoding of source files. #source_encoding = 'utf-8-sig' diff -Nru python-geopandas-0.7.0/doc/source/index.rst python-geopandas-0.8.1/doc/source/index.rst --- python-geopandas-0.7.0/doc/source/index.rst 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/doc/source/index.rst 2020-07-15 17:54:36.000000000 +0000 @@ -51,6 +51,7 @@ :caption: Reference Guide Reference to All Attributes and Methods + Changelog .. toctree:: @@ -58,6 +59,7 @@ :caption: Developer Contributing to GeoPandas + Code of Conduct Get in touch diff -Nru python-geopandas-0.7.0/doc/source/install.rst python-geopandas-0.8.1/doc/source/install.rst --- python-geopandas-0.7.0/doc/source/install.rst 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/doc/source/install.rst 2020-07-15 17:54:36.000000000 +0000 @@ -62,7 +62,7 @@ Creating a new environment is not strictly necessary, but given that installing other geospatial packages from different channels may cause dependency conflicts (as mentioned in the note above), it can be good practice to install the geospatial -stack in a clean environment starting fresh. +stack in a clean environment starting fresh. The following commands create a new environment with the name ``geo_env``, configures it to install packages always from conda-forge, and installs @@ -92,13 +92,13 @@ When using pip to install GeoPandas, you need to make sure that all dependencies are installed correctly. - - `shapely`_ and `fiona`_ provide binary wheels with the - dependencies included for Mac and Linux, but not for Windows. - - `pyproj`_ provides binary wheels with depencies included + - `fiona`_ provides binary wheels with the dependencies included for Mac and Linux, + but not for Windows. + - `pyproj`_ and `shapely`_ provide binary wheels with dependencies included for Mac, Linux, and Windows. - `rtree`_ does not provide wheels. - Windows wheels for `shapely`, `fiona`, `pyproj` and `rtree` - can be found at `Christopher Gohlke's website + can be found at `Christopher Gohlke's website `_. So depending on your platform, you might need to compile and install their @@ -143,15 +143,16 @@ - `shapely`_ (interface to `GEOS`_) - `fiona`_ (interface to `GDAL`_) - `pyproj`_ (interface to `PROJ`_; version 2.2.0 or later) -- `six`_ Further, optional dependencies are: - `rtree`_ (optional; spatial index to improve performance and required for overlay operations; interface to `libspatialindex`_) - `psycopg2`_ (optional; for PostGIS connection) +- `GeoAlchemy2`_ (optional; for writing to PostGIS) - `geopy`_ (optional; for geocoding) + For plotting, these additional packages may be used: - `matplotlib`_ (>= 2.0.1) @@ -159,6 +160,47 @@ - `mapclassify`_ +Using the optional PyGEOS dependency +------------------------------------ + +Work is ongoing to improve the performance of GeoPandas. Currently, the +fast implementations of basic spatial operations live in the `PyGEOS`_ +package (but work is under way to contribute those improvements to Shapely). +Starting with GeoPandas 0.8, it is possible to optionally use those +experimental speedups by installing PyGEOS. This can be done with conda +(using the conda-forge channel) or pip:: + + # conda + conda install pygeos --channel conda-forge + # pip + pip install pygeos + +More specifically, whether the speedups are used or not is determined by: + +- If PyGEOS is installed, it will be used by default (but installing GeoPandas + will not yet automatically install PyGEOS as dependency, you need to do this + manually). + +- You can still toggle the use of PyGEOS when it is available, by: + + - Setting an environment variable (``USE_PYGEOS=0/1``). Note this variable + is only checked at first import of GeoPandas. + - Setting an option: ``geopandas.options.use_pygeos = True/False``. Note, + although this variable can be set during an interactive session, it will + only work if the GeoDataFrames you use are created (e.g. reading a file + with ``read_file``) after changing this value. + +.. warning:: + + The use of PyGEOS is experimental! Although it is passing all tests, + there might still be issues and not all functions of GeoPandas will + already benefit from speedups (one known issue: the `to_crs` coordinate + transformations lose the z coordinate). But trying this out is very welcome! + Any issues you encounter (but also reports of successful usage are + interesting!) can be reported at https://gitter.im/geopandas/geopandas + or https://github.com/geopandas/geopandas/issues + + .. _PyPI: https://pypi.python.org/pypi/geopandas .. _GitHub: https://github.com/geopandas/geopandas @@ -177,10 +219,10 @@ .. _geopy: https://github.com/geopy/geopy -.. _six: https://pythonhosted.org/six - .. _psycopg2: https://pypi.python.org/pypi/psycopg2 +.. _GeoAlchemy2: https://geoalchemy-2.readthedocs.io/ + .. _mapclassify: http://pysal.org/mapclassify .. _pyproj: https://github.com/pyproj4/pyproj @@ -204,3 +246,5 @@ .. _GEOS: https://geos.osgeo.org .. _PROJ: https://proj.org/ + +.. _PyGEOS: https://github.com/pygeos/pygeos/ diff -Nru python-geopandas-0.7.0/doc/source/io.rst python-geopandas-0.8.1/doc/source/io.rst --- python-geopandas-0.7.0/doc/source/io.rst 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/doc/source/io.rst 2020-07-15 17:54:36.000000000 +0000 @@ -41,6 +41,17 @@ zipfile = "zip:///Users/name/Downloads/gadm36_AFG_shp.zip!data/gadm36_AFG_1.shp" +It is also possible to read any file-like objects with a ``read()`` method, such as a file handler (e.g. via built-in ``open`` function) or ``StringIO``:: + + filename = "test.geojson" + file = open(filename) + df = geopandas.read_file(file) + +You can also read path objects:: + + import pathlib + path_object = pathlib.path(filename) + df = geopandas.read_file(path_object) *geopandas* can also get data from a PostGIS database using the :func:`geopandas.read_postgis` command. @@ -105,6 +116,31 @@ rows=slice(10, 20), ) +Field/Column Filters +^^^^^^^^^^^^^^^^^^^^ + +Load in a subset of fields from the file: + +.. note:: Requires Fiona 1.8+ + +.. code-block:: python + + gdf = geopandas.read_file( + geopandas.datasets.get_path("naturalearth_lowres"), + ignore_fields=["iso_a3", "gdp_md_est"], + ) + +Skip loading geometry from the file: + +.. note:: Requires Fiona 1.8+ +.. note:: Returns :obj:`pandas.DataFrame` + +.. code-block:: python + + pdf = geopandas.read_file( + geopandas.datasets.get_path("naturalearth_lowres"), + ignore_geometry=True, + ) Writing Spatial Data @@ -114,6 +150,15 @@ :meth:`geopandas.GeoDataFrame.to_file` method. For a full list of supported formats, type ``import fiona; fiona.supported_drivers``. +In addition, GeoDataFrames can be uploaded to `PostGIS `__ database (starting with GeoPandas 0.8) +by using the :meth:`geopandas.GeoDataFrame.to_postgis` method. + +.. note:: + + GeoDataFrame can contain more field types than supported by most of the file formats. For example tuples or lists + can be easily stored in the GeoDataFrame, but saving them to e.g. GeoPackage or Shapefile will raise a ValueError. + Before saving to a file, they need to be converted to a format supported by a selected driver. + **Writing to Shapefile**:: countries_gdf.to_file("countries.shp") @@ -126,3 +171,10 @@ countries_gdf.to_file("package.gpkg", layer='countries', driver="GPKG") cities_gdf.to_file("package.gpkg", layer='cities', driver="GPKG") + +**Writing to PostGIS**:: + + from sqlalchemy import create_engine + db_connection_url = "postgres://myusername:mypassword@myhost:5432/mydatabase"; + engine = create_engine(db_connection_url) + countries_gdf.to_postgis(name="countries_table", con=engine) diff -Nru python-geopandas-0.7.0/doc/source/mergingdata.rst python-geopandas-0.8.1/doc/source/mergingdata.rst --- python-geopandas-0.7.0/doc/source/mergingdata.rst 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/doc/source/mergingdata.rst 2020-07-15 17:54:36.000000000 +0000 @@ -13,7 +13,7 @@ In an attribute join, a ``GeoSeries`` or ``GeoDataFrame`` is combined with a regular *pandas* ``Series`` or ``DataFrame`` based on a common variable. This is analogous to normal merging or joining in *pandas*. -In a Spatial Join, observations from to ``GeoSeries`` or ``GeoDataFrames`` are combined based on their spatial relationship to one another. +In a Spatial Join, observations from two ``GeoSeries`` or ``GeoDataFrames`` are combined based on their spatial relationship to one another. In the following examples, we use these datasets: @@ -31,6 +31,22 @@ countries = countries.rename(columns={'name':'country'}) +Appending +--------- + +Appending GeoDataFrames and GeoSeries uses pandas ``append`` methods. Keep in mind, that appended geometry columns needs to have the same CRS. + +.. ipython:: python + + # Appending GeoSeries + joined = world.geometry.append(cities.geometry) + + # Appending GeoDataFrames + europe = world[world.continent == 'Europe'] + asia = world[world.continent == 'Asia'] + eurasia = europe.append(asia) + + Attribute Joins ---------------- @@ -75,11 +91,11 @@ Sjoin Arguments ~~~~~~~~~~~~~~~~ -``sjoin.()`` has two core arguments: ``how`` and ``op``. +``sjoin()`` has two core arguments: ``how`` and ``op``. **op** -The ```op`` argument specifies how ``geopandas`` decides whether or not to join the attributes of one object to another. There are three different join options as follows: +The ``op`` argument specifies how ``geopandas`` decides whether or not to join the attributes of one object to another. There are three different join options as follows: * `intersects`: The attributes will be joined if the boundary and interior of the object intersect in any way with the boundary and/or interior of the other object. * `within`: The attributes will be joined if the object’s boundary and interior intersect *only* with the interior of the other object (not its boundary or exterior). @@ -96,25 +112,3 @@ * ``inner``: use intersection of index values from both geodataframes; retain only the `left_df` geometry column Note more complicated spatial relationships can be studied by combining geometric operations with spatial join. To find all polygons within a given distance of a point, for example, one can first use the ``buffer`` method to expand each point into a circle of appropriate radius, then intersect those buffered circles with the polygons in question. - - -Sjoin Performance -~~~~~~~~~~~~~~~~~~ - -Existing spatial indexes on either `left_df` or `right_df` will be reused when performing an ``sjoin``. If neither df has a spatial index, a spatial index will be generated for the longer df. If both have a spatial index, the `right_df`'s index will be used preferentially. Performance of multiple sjoins in a row involving a common GeoDataFrame may be improved by pre-generating the spatial index of the common GeoDataFrame prior to performing sjoins using ``df1.sindex``. - -.. code-block:: python - - df1 = # a GeoDataFrame with data - df2 = # a second GeoDataFrame - df3 = # a third GeoDataFrame - - # pre-generate sindex on df1 if it doesn't already exist - df1.sindex - - sjoin(df1, df2, ...) - # sindex for df1 is reused - sjoin(df1, df3, ...) - # sindex for df1 is reused again - - diff -Nru python-geopandas-0.7.0/doc/source/projections.rst python-geopandas-0.8.1/doc/source/projections.rst --- python-geopandas-0.7.0/doc/source/projections.rst 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/doc/source/projections.rst 2020-07-15 17:54:36.000000000 +0000 @@ -25,7 +25,7 @@ commonly used CRS is the WGS84 latitude-longitude projection. This can be referred to using the authority code ``"EPSG:4326"``. -*geopandas* can accept anything accepted by `pyproj.CRS.from_user_input() `_: +*geopandas* can accept anything accepted by :meth:`pyproj.CRS.from_user_input() `: - CRS WKT string - An authority string (i.e. "epsg:4326") @@ -64,11 +64,15 @@ From time to time, however, you may get data that does not include a projection. In this situation, you have to set the CRS so *geopandas* knows how to interpret the coordinates. -For example, if you convert a spreadsheet of latitudes and longitudes into a GeoSeries by hand, you would set the projection by assigning the WGS84 latitude-longitude CRS to the :attr:`GeoSeries.crs` attribute: +For example, if you convert a spreadsheet of latitudes and longitudes into a +GeoSeries by hand, you would set the projection by passing the WGS84 +latitude-longitude CRS to the :meth:`GeoSeries.set_crs` method (or by setting +the :attr:`GeoSeries.crs` attribute): .. sourcecode:: python - my_geoseries.crs = "EPSG:4326" + my_geoseries = my_geoseries.set_crs("EPSG:4326"}) + my_geoseries = my_geoseries.set_crs(epsg=4326) Re-Projecting @@ -98,6 +102,45 @@ ax.set_title("Mercator"); +Projection for multiple geometry columns +---------------------------------------- + +GeoPandas 0.8 implements support for different projections assigned to different geometry +columns of the same GeoDataFrame. The projection is now stored together with geometries per column (directly +on the GeometryArray level). + +Note that if GeometryArray has assigned projection, it is preferred over the +projection passed to GeoSeries or GeoDataFrame during the creation: + +.. code-block:: python + + >>> array.crs + + Name: WGS 84 + Axis Info [ellipsoidal]: + - Lat[north]: Geodetic latitude (degree) + - Lon[east]: Geodetic longitude (degree) + ... + >>> GeoSeries(array, crs=3395).crs # crs=3395 is ignored as array already has CRS + FutureWarning: CRS mismatch between CRS of the passed geometries and 'crs'. Use 'GeoDataFrame.set_crs(crs, allow_override=True)' to overwrite CRS or 'GeoDataFrame.to_crs(crs)' to reproject geometries. CRS mismatch will raise an error in the future versions of GeoPandas. + GeoSeries(array, crs=3395).crs + + + Name: WGS 84 + Axis Info [ellipsoidal]: + - Lat[north]: Geodetic latitude (degree) + - Lon[east]: Geodetic longitude (degree) + ... + +If you want to overwrite projection, you can then assign it to the GeoSeries +manually or re-project geometries to the target projection using either +``GeoSeries.set_crs(epsg=3395, allow_override=True)`` or +``GeoSeries.to_crs(epsg=3395)``. + +All GeometryArray-based operations preserve projection; however, if you loop over a column +containing geometry, this information might be lost. + + Upgrading to GeoPandas 0.7 with pyproj > 2.2 and PROJ > 6 --------------------------------------------------------- @@ -384,7 +427,7 @@ You can see that the CRS object constructed from the WKT string has a "Easting, Northing" (i.e. x, y) axis order, while the CRS object constructed from the EPSG -code has a (Northing, Easting) axis order. +code has a (Northing, Easting) axis order. Only having this difference in axis order is no problem when using the CRS in GeoPandas, since GeoPandas always uses a (x, y) order to store the data diff -Nru python-geopandas-0.7.0/doc/source/reference.rst python-geopandas-0.8.1/doc/source/reference.rst --- python-geopandas-0.7.0/doc/source/reference.rst 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/doc/source/reference.rst 2020-07-15 17:54:36.000000000 +0000 @@ -25,6 +25,10 @@ .. autoattribute:: geopandas.GeoSeries.interiors +.. autoattribute:: geopandas.GeoSeries.x + +.. autoattribute:: geopandas.GeoSeries.y + `Unary Predicates` .. autoattribute:: geopandas.GeoSeries.is_empty @@ -49,10 +53,14 @@ .. automethod:: geopandas.GeoSeries.intersects +.. automethod:: geopandas.GeoSeries.overlaps + .. automethod:: geopandas.GeoSeries.touches .. automethod:: geopandas.GeoSeries.within +.. automethod:: geopandas.GeoSeries.covers + `Set-theoretic Methods` .. automethod:: geopandas.GeoSeries.difference @@ -95,18 +103,29 @@ Additionally, the following attributes and methods are implemented: +.. automethod:: geopandas.GeoSeries.from_file + +.. automethod:: geopandas.GeoSeries.to_file + +.. automethod:: geopandas.GeoSeries.to_json + .. autoattribute:: geopandas.GeoSeries.crs .. automethod:: geopandas.GeoSeries.to_crs -.. automethod:: geopandas.GeoSeries.from_file - .. automethod:: geopandas.GeoSeries.plot .. autoattribute:: geopandas.GeoSeries.total_bounds .. autoattribute:: geopandas.GeoSeries.__geo_interface__ +.. automethod:: geopandas.GeoSeries.isna + +.. automethod:: geopandas.GeoSeries.notna + +.. automethod:: geopandas.GeoSeries.fillna + + Methods of pandas ``Series`` objects are also available, although not all are applicable to geometric objects and some may return a ``Series`` rather than a ``GeoSeries`` result. The methods @@ -117,7 +136,7 @@ GeoDataFrame ------------ -A ``GeoDataFrame`` is a tablular data structure that contains a column +A ``GeoDataFrame`` is a tabular data structure that contains a column called ``geometry`` which contains a `GeoSeries``. Currently, the following methods/attributes are implemented for a ``GeoDataFrame``: @@ -128,18 +147,32 @@ .. automethod:: geopandas.GeoDataFrame.from_file +.. automethod:: geopandas.GeoDataFrame.from_features + .. automethod:: geopandas.GeoDataFrame.from_postgis +.. automethod:: geopandas.GeoDataFrame.to_crs + .. automethod:: geopandas.GeoDataFrame.to_file .. automethod:: geopandas.GeoDataFrame.to_json +.. automethod:: geopandas.GeoDataFrame.to_parquet + +.. automethod:: geopandas.GeoDataFrame.to_feather + +.. automethod:: geopandas.GeoDataFrame.to_postgis + .. automethod:: geopandas.GeoDataFrame.plot .. automethod:: geopandas.GeoDataFrame.rename_geometry .. automethod:: geopandas.GeoDataFrame.set_geometry +.. automethod:: geopandas.GeoDataFrame.explode + +.. automethod:: geopandas.GeoDataFrame.dissolve + .. autoattribute:: geopandas.GeoDataFrame.__geo_interface__ All pandas ``DataFrame`` methods are also available, although they may @@ -147,8 +180,22 @@ return a ``GeoDataFrame`` result even when it would be appropriate to do so. -API Pages ---------- +Testing +------- + +GeoPandas includes specific functions to test its objects. + +.. autofunction:: geopandas.testing.geom_equals + +.. autofunction:: geopandas.testing.geom_almost_equals + +.. autofunction:: geopandas.testing.assert_geoseries_equal + +.. autofunction:: geopandas.testing.assert_geodataframe_equal + + +Top-level Functions +------------------- .. currentmodule:: geopandas .. autosummary:: @@ -158,8 +205,13 @@ GeoDataFrame GeoSeries read_file + read_parquet + read_feather + read_postgis sjoin overlay clip tools.geocode + tools.collect + points_from_xy datasets.get_path diff -Nru python-geopandas-0.7.0/environment.yml python-geopandas-0.8.1/environment.yml --- python-geopandas-0.7.0/environment.yml 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/environment.yml 2020-07-15 17:54:36.000000000 +0000 @@ -0,0 +1,33 @@ +name: geopandas-dev +channels: + - conda-forge +dependencies: + # required + - fiona>=1.7 + - pandas>=0.23.4 + - pyproj>=2.2.0 + - shapely>=1.5 + + # geodatabase access + - psycopg2>=2.5.1 + - SQLAlchemy>=0.8.3 + + # geocoding + - geopy + + # plotting + - descartes>=1.0 + - matplotlib>=2.0 + + # testing + - mock>=1.0.1 # technically not need for python >= 3.3 + - pytest>=3.1.0 + - pytest-cov + - codecov + + # spatial access methods + - rtree>=0.8 + + # styling + - black + - pre-commit diff -Nru python-geopandas-0.7.0/examples/choro_legends.ipynb python-geopandas-0.8.1/examples/choro_legends.ipynb --- python-geopandas-0.7.0/examples/choro_legends.ipynb 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/examples/choro_legends.ipynb 2020-07-15 17:54:36.000000000 +0000 @@ -0,0 +1,542 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import geopandas\n", + "from geopandas import read_file" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'2.2.0'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import mapclassify\n", + "mapclassify.__version__" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'4.2.0'" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import libpysal\n", + "libpysal.__version__" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Name Description Installed\n", + "0 10740 Albuquerque, New Mexico, Census 2000 Tract Data True\n", + "1 AirBnB Airbnb rentals, socioeconomics, and crime in C... False\n", + "2 Atlanta Atlanta, GA region homicide counts and rates False\n", + "3 Baltimore Baltimore house sales prices and hedonics False\n", + "4 Bostonhsg Boston housing and neighborhood data False\n", + "5 Buenosaires Electoral Data for 1999 Argentinean Elections False\n", + "6 Charleston1 2000 Census Tract Data for Charleston, SC MSA... False\n", + "7 Charleston2 1998 and 2001 Zip Code Business Patterns (Cen... False\n", + "8 Chicago Health Chicago Health + Socio-Economics False\n", + "9 Chile Labor Labor Markets in Chile (1982-2002) False\n", + "10 Chile Migration Internal Migration in Chile (1977-2002) False\n", + "11 Cincinnati 2008 Cincinnati Crime + Socio-Demographics False\n", + "12 Cleveland 2015 sales prices of homes in Cleveland, OH. False\n", + "13 Columbus Columbus neighborhood crime False\n", + "14 Denver Demographics and housing in Denver neighborho... False\n", + "15 Elections 2012 and 2016 Presidential Elections False\n", + "16 Grid100 Grid with simulated variables False\n", + "17 Groceries 2015 Chicago supermarkets False\n", + "18 Guerry Moral statistics of France (Guerry, 1833) False\n", + "19 Health Indicators Chicago Health Indicators (2005-11) False\n", + "20 Health+ 2000 Health, Income + Diversity False\n", + "21 Hickory1 2000 Census Tract Data for Hickory, NC MSA an... False\n", + "22 Hickory2 1998 and 2001 Zip Code Business Patterns (Cen... False\n", + "23 Home Sales 2014-15 Home Sales in King County, WA False\n", + "24 Houston Houston, TX region homicide counts and rates False\n", + "25 Juvenile Cardiff juvenile delinquent residences False\n", + "26 Lansing1 2000 Census Tract Data for Lansing, MI MSA an... False\n", + "27 Lansing2 1998 and 2001 Zip Code Business Patterns (Cen... False\n", + "28 Laozone Ozone measures at monitoring stations in Los ... False\n", + "29 LasRosas Corn yield, fertilizer and field data for pre... False\n", + "30 Line Line Shapefile True\n", + "31 Liquor Stores 2015 Chicago Liquor Stores False\n", + "32 Malaria Malaria incidence and population (1973, 95, 9... False\n", + "33 Milwaukee1 2000 Census Tract Data for Milwaukee, WI MSA False\n", + "34 Milwaukee2 1998 and 2001 Zip Code Business Patterns (Cen... False\n", + "35 NCOVR US county homicides 1960-1990 False\n", + "36 NDVI Normalized Difference Vegetation Index grid False\n", + "37 NYC Demographic and housing data for New York Cit... False\n", + "38 NYC Earnings Block-level Earnings in NYC (2002-14) False\n", + "39 NYC Education NYC Education (2000) False\n", + "40 NYC Neighborhoods Demographics for New York City neighborhoods False\n", + "41 NYC Socio-Demographics NYC Education + Socio-Demographics False\n", + "42 Natregimes NCOVR with regimes (book/PySAL) False\n", + "43 Nepal Health, poverty and education indicators for ... False\n", + "44 Ohiolung Ohio lung cancer data, 1968, 1978, 1988 False\n", + "45 Orlando1 2000 Census Tract Data for Orlando, FL MSA an... False\n", + "46 Orlando2 1998 and 2001 Zip Code Business Patterns (Cen... False\n", + "47 Oz9799 Monthly ozone data, 1997-99 False\n", + "48 Phoenix ACS Phoenix American Community Survey Data (2010,... False\n", + "49 Pittsburgh Pittsburgh homicide locations False\n", + "50 Point Point Shapefile True\n", + "51 Police Police expenditures Mississippi counties False\n", + "52 Polygon Polygon Shapefile True\n", + "53 Polygon_Holes Example to test treatment of holes True\n", + "54 Rio Grande do Sul Cities of the Brazilian State of Rio Grande do... False\n", + "55 SIDS North Carolina county SIDS death counts False\n", + "56 SIDS2 North Carolina county SIDS death counts and r... False\n", + "57 Sacramento1 2000 Census Tract Data for Sacramento MSA False\n", + "58 Sacramento2 1998 and 2001 Zip Code Business Patterns (Cen... False\n", + "59 SanFran Crime July-Dec 2012 crime incidents in San Francisc... False\n", + "60 Savannah1 2000 Census Tract Data for Savannah, GA MSA a... False\n", + "61 Savannah2 1998 and 2001 Zip Code Business Patterns (Cen... False\n", + "62 Scotlip Male lip cancer in Scotland, 1975-80 False\n", + "63 Seattle1 2000 Census Tract Data for Seattle, WA MSA an... False\n", + "64 Seattle2 1998 and 2001 Zip Code Business Patterns (Cen... False\n", + "65 South US Southern county homicides 1960-1990 False\n", + "66 StLouis St Louis region county homicide counts and rates False\n", + "67 Tampa1 2000 Census Tract Data for Tampa, FL MSA and ... False\n", + "68 arcgis arcgis testing files True\n", + "69 baltim Baltimore house sales prices and hedonics 1978 True\n", + "70 berlin Prenzlauer Berg neighborhood AirBnB data from ... True\n", + "71 book Synthetic data to illustrate spatial weights True\n", + "72 burkitt Burkitt's lymphoma in the Western Nile distric... True\n", + "73 calemp Employment density for California counties True\n", + "74 chicago Chicago neighborhoods True\n", + "75 clearwater mgwr testing dataset False\n", + "76 columbus Columbus neighborhood crime data 1980 True\n", + "77 desmith Small dataset to illustrate Moran's I statistic True\n", + "78 geodanet Datasets from geodanet for network analysis True\n", + "79 georgia Various socio-economic variables for counties ... True\n", + "80 juvenile Residences of juvenile offenders in Cardiff, UK True\n", + "81 mexico Decennial per capita incomes of Mexican states... True\n", + "82 networks Datasets used for network testing True\n", + "83 newHaven Network testing dataset False\n", + "84 nyc_bikes New York City Bike Trips False\n", + "85 sids2 North Carolina county SIDS death counts and rates True\n", + "86 snow_maps Public water pumps and Cholera deaths in Londo... True\n", + "87 stl Homicides and selected socio-economic characte... True\n", + "88 street_net_pts Street network points True\n", + "89 taz Traffic Analysis Zones in So. California False\n", + "90 tokyo Tokyo Mortality data True\n", + "91 us_income Per-capita income for the lower 48 US states 1... True\n", + "92 virginia Virginia counties shapefile True\n", + "93 wmat Datasets used for spatial weights testing True\n" + ] + } + ], + "source": [ + "libpysal.examples.available()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Downloading South to /home/jovyan/.local/pysal_data/South\n" + ] + } + ], + "source": [ + "_ = libpysal.examples.load_example('South')\n", + "pth = libpysal.examples.get_path('south.shp')" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "df = read_file(pth)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## New default legend formatting" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "%matplotlib inline\n", + "ax = df.plot(column='HR60', scheme='QUANTILES', k=4, \\\n", + " cmap='BuPu', legend=True,\n", + " legend_kwds={'loc': 'center left', 'bbox_to_anchor':(1,0.5)})" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['[ 0.00, 3.21]', '( 3.21, 6.25]', '( 6.25, 9.96]', '( 9.96, 92.94]']" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "labels = [t.get_text() for t in ax.get_legend().get_texts()]\n", + "labels" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Quantiles \n", + "\n", + " Interval Count\n", + "----------------------\n", + "[ 0.00, 3.21] | 353\n", + "( 3.21, 6.25] | 353\n", + "( 6.25, 9.96] | 353\n", + "( 9.96, 92.94] | 353" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q4 = mapclassify.Quantiles(df.HR60, k=4)\n", + "q4" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "labels == q4.get_legend_classes()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that in this case, the first interval is closed on the minimum value in the dataset. The other intervals have an open lower bound. This is now displayed in the legend." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Overriding numerical format" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "ax = df.plot(column='HR60', scheme='QUANTILES', k=4, \\\n", + " cmap='BuPu', legend=True,\n", + " legend_kwds={'loc': 'center left', 'bbox_to_anchor':(1,0.5)},\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgEAAADFCAYAAADNACHqAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjIsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8li6FKAAAgAElEQVR4nOy9d5wcZ334/35mtu/t7pW9XnSSTu3UbEkuyHLvBWMHGwyxqaaGEEggJE4Cof4oCSGEgAHzI/RiwMa4Ile5yFbvOulO0vV+2/uU5/vH7O3tqdgC27ho3q/XvW5n5nmeeWZ2dp7P82mPkFJiY2NjY2Njc+qhvNIdsLGxsbGxsXllsIUAGxsbGxubUxRbCLCxsbGxsTlFsYUAGxsbGxubUxRbCLCxsbGxsTlFsYUAGxsbGxubUxTHK92B4xEOh2V7e/sr3Q0bGxub1wxbt26dlFLWvtL9sHlt8aoUAtrb29myZcsr3Q0bGxub1wxCiL5Xug82rz1sc4CNjY2Njc0pii0E2NjY2NjYnKLYQoCNjY2Njc0pii0E2NjY2NjYnKLYQoCNjY2Njc0pii0E2NjYvObQTBPDXgHVxuZFYwsBNjY2r0lsGcDG5sVjCwE2NjavOSLJPLF0/pXuho3Na56TFgKEEKoQYrsQ4t7idrUQYr0Qorv4v+oE9a4QQhwQQvQIIf7ppeq4jY3NqctUIk8irb3S3bCxec3zp2gC/g7YX7b9T8AjUsoFwCPF7VkIIVTgf4ErgU7gbUKIzj+/uzY2Nqc6hmmiqgKvW32lu2Jj85rnpIQAIUQLcDVwR9nuNwE/Kn7+EXDdcaqeCfRIKQ9LKQvAL4v1bGxsbP4sTGBOfYDakPeV7oqNzWuek9UEfAP4R6zf3zT1UsoRgOL/uuPUawYGyrYHi/uOQQjxfiHEFiHElomJiZPslo2NzanGjEOg7RloY/NieUEhQAhxDTAupdz6Z7QvjrPvuL9cKeX3pJRrpJRramvthbBsbGyOxZCybCZyvNfLa4OCbqAb5gsXtLF5mTmZVQTPAa4VQlwFeICgEOKnwJgQolFKOSKEaATGj1N3EGgt224Bhl9sp21sbE5NDPO1P/vfOxBlb38MgPOXNlBfaZs1bF45XlAIkFL+M/DPAEKIC4BPSClvFkJ8DXgn8OXi/98fp/pmYIEQYi4wBNwEvP2l6bqNjc2phJSSDXtGSGY1S50oYWlbFXMaAseUFcyoHMs/H+94OblEnv49o4z3xlh19SJ8Ic+s8/cMJ+gZjuNxOdB0A92UpYauWNOKoojZbUvJtp4phiOZ0l5TWudf0V5NXVn7NjavBC8mT8CXgUuFEN3ApcVthBBNQoj7AaSUOvAR4CGsyIJfSyn3vrgu29jYnGqYUpLVTZa0VqEZEt2QNFR5ZwkA5caBF9IXyGJ5AWTyOppusvE3e7jj7+7lj9/fgtEaZOOhSTbsHiGb1wEYjWTYPxArnt/E73GiGxLdlNRVelGU45gnhOD0jhrm1PmL1wGqAg5V0F5XgRCvXZOGzeuDkzEHlJBSPg48Xvw8BVx8nDLDwFVl2/cD97+YTtrY2JyaSCkxpMSQoCiCmpCHK1a3MDyVIZbOMTCRIuRzEfA5QYgTzvDLmR52JdA3lmTnkQgAgfYQrauaSEUzxJ0KFAwUzSi1ODSVKbWRLRhkCwZuh0JeN5nfGJzp81HnU4Rgzfwwmm4yOJXBqSrkNdMWAGxeFdgZA21sbF5VGFKSN0x00yRvSvTiqDo9c3c5VdobAqRyBtsPTfH47hFiqXxJAHihoVUW/0am0iUBACCpm5gFHYcUuDI6PpeDq85ow+t2koqlGY1mZrXTWusj4HNSE3BR0A2klDz3271sucdKp6IKcAqBakr2PtVH/NlB5LODyHQBCezpj2C+DnwcbF7b/EmaABsbG5uXCyklupRMm9l1OWO7nx7cZVnZ8oyBE/Ecui6ZSuWJp/OsWVCLQ7XmOMfzBxiLZtjcPTlrvyutcWjfBNKURAbjnPXmTqQpiU3E+Mj89+Gr9HPO31zLRbdeTnONH0URTMSyPHdgnOcOTDCvIYAn6GayN4ICqEIgJNz3/c0MHonSdGYzgfpaXEEPpmbSM5JESlg9P/xy3E4bm5PCFgJsbGxmIaWcsZkLYWXoU15+paEhrb9pjlbtT382TEnvaJLqgIt0Tied1xmLZekajJfKqopyQrOABOqqfFxzZhuprMZ4PEs0mWPqcBRpSirqKwjPq+KZX+2he+MgqckeCrkChdECYjJKa21Fqa3aSi8Xrmhia88ktUEP9Zd2ILAEAEUIBg5McGTPGAtuOY2EaeJxKbgcClrO8jNIZOzUxzavLLYQYGNjMwtDYs3ITckz+0ZRhGBxSyVN1b6X/FzTsfJCCJ7ZP0aF10l7XQU+r/OEHv2RRI69/dHS/nOXNlBZ4eLwSIK9/TGWtlUixIl9A6bbURRB0O8i6Heh5XykKjysOKOFDb/bi7vCjavCiVFIsf6OB0t1//i9B3G4HNz0uZtRnNbr0zAl6ZxOvmgSQAhrmWNTUt1WyYWfOpd9g3EUAQ5FQSnzBUhkCyQyBYI+14u+lzY2fw62EGBjYzMLURwlhYB4RqOqwsVT+8e4dGUTVRXul+w8hil5fM8o6ZzGus56NMOkZyRBLJXjnKWNswbxcq1AbchDS42PwaKjXsjvQgjB/MYQjdU+vG7HSYcETh9zepxUNYfITqXxnd9ORkoaI7UM7Bjhmo+9D2SWrme2kY5FufEzb0dxOhDAhp8+xoHdfdS+6Xx2HI7gcqg0VPsQAoYjWYSwhI3VHWEm41n6JtJ4XNaaB1JKxFiabcPdnP+mTttR0OYVwRYCbGxsZqEU/4zitpBQU+Hm0EiS1R2ul2ywGpxME0lZywHv7osylbQ+B/2u54/xF4JVHWFawlkyeaMUmicE+DzO456rvL2CbhJN5qmr8s46ls3rHBlNogioUlW6DkcxDUlsPEvzojBNi1dz2ftW43Bb5zClpOm8lWx7fA9h00QoCk7V6osqBDUBNx6XSmOVD01KagJu+ibS6LE8nqEE6f44B/eMAeA04bSL5+O38wbY/IWxowNsbE5RpLRi3Hu2DxOfSLPx9/voOzhBLKuRLegIIbhyTQsTyTxTqTyxTJ4/bB7g0d3DPPFoD91bh2a1dXTbk4kcR8aSJzx/7/jMsUgyz3SYvc89M5DLsv/loX0IQXXQQ76gk8lpxwgLY4ciHA8J7O+P8uyBcXYcmkTTTaSUDE+leWbfGLph4nWpTBkmHW9ZitNrzZPG+6J4A24evH0LT/50B4mJNEII5jSGmHvrtSgOq9wDX/sNXU/uQRECj0tFYkU7IGHj/jFUzSA3leHg/QcZKgoAABvv3sfPP//oCe+Vjc3Lha0JsLE5hdGlpHlZPXd/7UkUj0o67CM2laazNcS8xhCKouB1KWQLJgXdJJvXSEcTpB7uZ/NYmtWXL2DV1YtwB9woSFQhQEBeM3hm/xhCOX5SHCvZjoNK3cWaBeFiBj6Tx3cNHyNQwLEaAdOU7OmN0j+Romc0ycWnNeFxWa8zLa/zh288jdvr4OYvX4EoS+IzGc8yOJUGoH8izWQix/yGILv7okgpcY5OoDfWIQyT1N4JPD4XWlZHyxlU1lWQTRXY9ehhujb2c9Nnzueb7/4GisuJqRu0X7yKwf0DPPBfd/Ob7O8QgKmZKAJMAclHDtO/d5zWzuOttQapaNYyEdhmAZu/ILYQYGNzilKaXSuC6z91HgDP7R/HjGfZ0xfDNKGx2ktNwEssncc0Jbu/8lN23beZyz94I4rqxzBlaZA1sVTkSDg4FCenW05/D+8cZmFzkLbwjDDwzIFxcgWDOXX+kgp/LGbZ+PcPxIilCrhdKg2VXmorvbPU+UjYdGCc8XgOmIkWWNxWRT5T4Olf7CKXKnDuTStmCQCmKdndG0EvC0HI5A0GJy2hIP7kdu697Ydc8tHr8fuaiQ5ZmormhWGGDk4SHZ1g54MPcdpl61i4Zhn/dv6nGOkZmbmfmqCyKYSW1xjrHaNhbgODByf53X8+idPtIJ/RcHpUgp11sPfYpVYMzSSX1vBW2E6CNn85bCHAxuYUpKAbmMeZcZ6xqJZn9o8RSeYZiWQYmExR0E3ymjWgv/O7H8VXKFDZWIXqUItehNbgHBmJUNVYjQAWt1VRG/IyFEnjdTqor/Kjm5JcQePwaIqJeA7DlPjiKnPqg4xMpekbT5EvCg5DEUsgaKutONY/QBSFjSJSSpL7JvjZdzaRTRXIpQoANC+qnVVPVQRnL65na88EkVgOT7JArtJDMlsgJAz2PrkLgIe/eRdrb7gIh7cVocDkUJymhdXsemg90ZEIj/3oHk6/bCH+kJ/z3nY+fXv6KOQMwnOW4fE7edPf38iXrvsCX3r8y+x9shfTkOSLoYALruskVelmyUfOskIwpaT7p+vp39zFmmvWomsaYAsBNn85bCHAxuYUIpcuEBtLcd93N7Hqig5yqQiP3PEQmUSGYDjIyrdfhNLeTH3Iw1hxpl3jcyETafTuCImsQeuF8wAY2NNHqL6SYG2InX/czlff/EU+/dDnWbh2iVUv5KEm5CkNwibgdDloqfWTzmvkNQPThFgqT+9Ykmhx8C4nkswR8luDYrk54OzF9fSOJdjdGyX7x0M81xubVe+KD5xJRdhXqjfdB6/bwbqljcRTeXYcniKX0cgcHubJb/6WxGiUmqYapoanEIpC88Iwk4Nx9LyOqUs6zj6XpsULefqXD7D0opWsfet5mMCRTQdJTCbZ8eg4yakMBXeAS269DofLwVUfOJOa5iD7nu6jujGAWevDSGSJZ3OoHjdmOsvgrh4OPtfFR77/EQJV/pfsu7axORnE8exvrzRr1qyRW7ZseaW7YWPzmsU0JZph4naqpX0Tg3F+/tlH0PJGaV//zg307jpS2r7pt/+Oo76GqgoXsZSV3rZSFfT+/gCJoQRCEay8eD6Ht23l3m/cTWNHI+/8j/fy40/+/wx3D9N57lL+5f7P4vJaHv6x0STbHzhI765RLrjldOauagJgYDzF9sNTpfP63Q58bhVDWk6CAA5FEPK7qK5w43IqNFT78Jd5/wtA0wxuf//ds679DW/uZPVVi0umgBO+4aRkX1+MwakUxFMMbNiJu+BGkW4io1kAXF4HwRo/k8VEREIBIz9ILpXgPf/zQZzVIfb0Rlg1rwZtKsPgwSkmB2LseuII571lOWdevRgAQzcRCvz3e7/Jkz9/nGA4SC6VI5PIoDpU3vtft7L0vGXMXTH3ZL/iYxBCbJVSrvmzG7A5JbE1ATY2r0OOjCcZiWRZ11lf2nfguQEKeR2nUwcEvhoHl73/74jt6+W5Ox7AiYo3JvH7CkSBmoCbTF4nVjBoWTeHg7/dDjLNwN5BoJ6Vl57NzvXP8u1bv0nzomaGu4fJJDIYuo4sqrSHD0yyd0MvCNALeqkv5cJJpd9Jqpj5DyAcdDOZyFNV4WYikSuFDvZPpLlwReOshYIcDoW2pXX0F23sDrfCqisXgSJKg7+umzgcM4FQ5amIJ5M5cpoJPh+1V7wBRUD6iV2MbtiE2++luqmOmFEFuJDSJBs5wuY/PAXA1NAUX3jqawS8Trb0TJLMFgg3V7DijGYWntnKvqf7SudUi+c//6ZzeeoXT5CYTFDdVE0mkeFD3/kwC85YQFtn24v70m1s/gxsIcDG5jWCacrjL1eLZSOfTvOrmdYKdcPRDE/vHyMc9BDwOOjt7mHz735NLm2p+W/47w+TESqupfO55EsfoP83+zj0eC+hej+h65bgUAQF3dIaxAppnvrFnRRyBZZdcBrVrSupm7eU2+69nIZ5DdQvaOL/PvY9Hvjf+/jS1Z9l+UUrmLd6CYWsE0UVmIZk/Q+24K/y0rSoFp/HQbVHRXE5cCgKsbJ1ACKRDFVuB/m8Pusak1mNDXtGOXdZQ8kXASG49h/WoRcMVJd6jGd9Nq/z2M5hWmsrWNgSwu1US8LBD//+DmpuvAShKHhdKooQFEZi7PjdUxzasr/UhqIqXPmhtzIxMFgSAABGukeIDE6yYn4jT+0ZIehzk0rm2PXQGPMX1XHJO1cd8z2loimqGqpYesEqVIcPb9DL2devQwhp+VjY2PyFsc0BNjavYrZvOELA7yJY62fDL3fhcKm4K1ycd/Np+D1OJhI5YukCrXUVqAJUQCsuwKNpBk/tGyWZ1an0O8nkdJ7+1zs48MgOzvuba2l726V4XSqGIZlb4yO1b5x8YwWp4rK9EiuxTsjnJDM8Sdc9G9n26yeoX9jCVV/7MGaqwNI1LdRVeTE0nQ+0vZvEhKU2b17UzKor13DLf7wXUzfpfraPOz/3I7xBN2/57Lt5+I6tVIR9uC6ci1AEHqeK16USTRdQd41xZOOAJTBcOg8cCjm/C6EI/G6V81c0lRYHKufoVQQNU/LoziEyRfOHQxW0V6gMPbGT/j19PHT7A1zzhXdRecFqAJxZjcHf7EMrGFTXuejdtYdtDzwNwHX/+EESUwUUYmz6/aPopsEV//J2Hv3ar/n24TvQUegfT9I1EMN9JE73+h6CYR9Xf/AsmhfOXiBovC/GL7/0GFre4K3/fD5un4va1tCLflZsc4DNn8MLagKEEB5gA+Aulv+NlPIzQohfAYuKxSqBmJTytOPU7wWSWAnIdPshtbF5YXTDpH8yTcTnYNOvd6HoklQsi6GZOH1OCsvraKjykc5rRFMFCrrB/KYQRlkbTqfKucsa2XxggrxmUDAkaz9/K+d9IkEuGEBKy7P+klXN1qA6r4YNu0fIpQsoAtYsqGXH4Sl0Q5IPBmm/+XLmvGkdj3/8WwzvOIB38Rw2do1x9ZltOBwqX930df7rbV9Ddarsf2ofwdpuK+5dFajOArse3QaAEC6c/jZyqQJthomhqOQ0A/J55KEhUqkUDqdOOpql+9d7AQjPqaR+VRPpeh9d/TGWtlfNmvVPCwA9mw4yZ+U8+naMUNEWIlvm/+BDMLB7jAe/9SBDXb0APPrVX3Pd2cvxBTxEH+orefF7AhVUhDtYd1OAoa4jREayxVYCrHvbNbS+ZQ1pobL8+iE+d8m/cu7bz2fJuUs5r7OejXsmqJ9bRS6Vx3dUBkApJfuf7aeQ1bngbStpKUYw2Ni8UpyMOSAPXCSlTAkhnMBTQogHpJRvnS4ghPhPIH7CFuBCKeXk8xy3sbEpYpgmT3eNMRaz1PZzru9kz7c3IRRoXFDDvOs7GYhn6ZtIlepMJayQu/a6AC7XjFq5byzJRCJX2tZMSXVLLa6CQTqvs7C5EnU6zt+UOBRBOOhBFVBV4eKK1S10D8XZX3SMM6XJUNcgd/3tt2heNIcL33sjYyEfTUtqGdw/yDUfu5bffOHX1mD31D4e/u6zDOyfwu13cv2X3kcml6F13XKyPSkGNg5gbB/FtbwOoZsMbdzOA5/7GQDVTTWsuOxKTMO6loJmoIR9mBLG4lmWUUW54l9iDbDVTfXc941nGOyaYMkNS5E1VmrgSq+Tru9voZDWWHPNFXj8T3Bo634qqiu4Yk0LngovrGohm8jz89t+Ss/mQ7gD83D7K+k8/0KCNT5WXDKffEZjwy93Eds0Qs3aNjrecQVthwb4zW0/pP0Pm/jnez/DG65YyM8/+wjzTmukqr6CcgYPTLL5vgPUtoVYfcWCl+aBsbF5EfxJ5gAhhA94CviQlPK54j4B9GMJCt3HqdMLrPlThADbHGBzKqMbJg9sGyRbMFAEXLCiiQqPo+QQV8jrCFVhaDLNeDxDtmASS1vhdUvbKulomlYtS3rHUvSPp4gWj9cE3Ewl84SDbgQwkcjjcam4HQpup8p4PIfMFYhv2kN012Fu+eq7qG6sZkv3JMORDOnt+9l6+70YhmTVVVcTn7BmyPHhXex9cjd6QcfhdNC2fA65VI5VV11CohgEsPxDZxI3zdJ1hkzY+3/bEBIgz9O/+A2F3EyY4PKLVlHdtoJF13fiaA6QKC6/e8XqFlxljoXjhyPseriH+EQaRREMHbROqDgVOt++ErPCSXJDHwO7Z9L0VtV7MfQRbvnae2ZpFGKjUT55+kdp6GhkpGeEXDJLxxmLaVmyiFu++ha6nu6josrL/qf7ySZyeMJ+XItrUJyCx//9h1xy82UsWttJU0cN+VSBUM3MyosT/VHu/MrjGIbgpn+58CUxAZRjmwNs/hxOyjFQCKECW4EO4H+nBYAi5wJjxxMAikjgj0IICXxXSvm9E5zj/cD7AdrabC9Zm1MTKS3nv4tWNJHKafg9ThwOBSkl44em2PtEL4oquOCdq5jTEKB3PEm8bE36php/WUicYE59gDl1FTy0bRCPUy152mu6lSM/6HOSyGjouklNwM3Zi9vo23mET972QwB2PLiFf3/ki6xZMRdNM7j1yn8km8zicDmIDvWQiSdJReP07ektef/rmo7H78HlcZFNTAFVgAJlAgBAXIElHz4Tx1SWnj90cfmH347q8JCYGGH8yCD7n9rFu373ZjKVXtSCQTjoZm59AJdTxTBNUlkNJWew8bd7GdhnRQc0zKsqtV9dH2DqqT6iYymklDQvCpNI5kkMJYiPD7H5nkcJt4a58qPXIoTANAy+9c6vE5+IM/f0DjrPXYOuaSQnx3nHf7wFl9eFP+RFLxhc8/FzWH/7Y3RvHkBuG8bhFCxefTpHdgyx98lJFl/WQeMbWjm92ltaOvjur9/J7ke38Q8//beXXACwsflzOSkhQEppAKcJISqBu4QQy6SUe4qH3wb84nmqnyOlHBZC1AHrhRBdUsoNxznH94DvgaUJ+JOuwsbmNU4yq7H10CQLmoLUhLyoDoVQhRvdMOnZMsj2+w8ydiQKQCDs4+BAlERWn5UCF9Nk64PdNDQG6DizZWa/EFy+uhWwhIxsTufJvaMIAYmMhtupYhgm8xqDIARtK+bywe9+hNs/8C28NUGaF7eCEDhdDm7+/97J9z9yO3pBZ7J/kCM7exGKwpJ1y9GyBq3LFuL0VBIZ6OHpOx+m65n9dK5bTsvpa9GcCmgzgkA46CaSzOMMuclmNMy4BLJAJdWtlbz1yxeTqwwClpPfZCKPx6mSzGr0jqXQDZPJu/ajeh3Mu2geFQuqyY+niUykqWsKMnhgtvIxlyzQ2FmH16mw4WcPk46l+b9/+AFdz+zjrf9+M8/+9il2PbKTyz90I9mUNYP3exx89McfweWzllDuOLsVgeXl/7sv/4RUJMnc0zpwe10Mdw/ygds/xvY/DhI/FGHJxfPpn0jRXhfANE32PL6bW75wCx2rm1/ah8fG5kXwJ4UISiljQojHgSuAPUIIB/BXwOrnqTNc/D8uhLgLOBPL0dDGxqbISDTDeDxHPF1g3dIGvG4H/eNJuocTJJ7uKwkAAHOuXkjXUKK0HfA40A5HGXqqn/6opZ6/dUktnoA1cJUvviOE5fWf1000wywl6VneXl3K4S8UwXnvvJhsTTVGQy0HR1Msbq0kNpIkHfNx3l9fTXIyQuOiFdR1nEWo1ovTZXBw4ya0vEImmSVYZzm8OT0uTFMjvKKCdDoPTgeeVAE1kmNyjjUbzusmS96/hvTuMXo3WLH1DXOryMcLhDWD+Qtr8XucHByKMThlpRN2qhDyu3HesJSsZuD1OollNcId1Sxrr2Tn/5QrK62bIKXk4MOHAFh7w9Xc980fIaUkNhzj71d8hEA4SNvSNhafMw9/qJa9G44QHU1xz38+zfzVjVQ3uZm7ej6H9wyjBTz81c9uw0hm6H9iJ4WhcQ5tyzJ/TTud6xbxzG/30lbjR3VaUQwT/RNoeY21bz7npXlgbGxeIk4mOqAW0IoCgBe4BPhK8fAlQJeUcvAEdf2AIqVMFj9fBnzupem6jc1rDyklmikZjWaIpwvMbwjiczuY3xDk0GiSZFYjksxRjYd9/XF006RyXRu5aJJIbwoEjG08hPfCRaU2C9EcPfceQEpwuAoU0lNs+MmTXPCe83B5XLNX3zNMxqLZ0vbi1kqaw1aq2vHDEUINFWRNyfZDkxgN1kDeM5Igls7T+7NdRIaToIRZcPYCRg9bgkl8Iktq8gB7ntjJ0IF+Vl19Cam4lzPe+Ab69x6m65kuhg4M8Y/3fIaNv95HKpJFCJj/9pUUKpxgmBTG0yR7Y9Q0BTFNk9EjUWpag1y4th1RDAes9Ney/dAUuYKBZphk8zpZzfL+d6oCv1OhkNEo9MYIhn04nCoTA8WQxYVhhso0A5HRAjf864eJjqZRHTkmBiJMDY6RmIiz4Ix51M1v5LQrFxIbi3HXF3/HTz71W0YPDVPTXEPb6fNZ/LG3IJxOpjZ38dS3/8Cisxex9oZzOPDkXvZs2M0tX3svikMpOS/Wzanj9MuPzRtgY/NK84KOgUKIFcCPsEKQFeDXUsrPFY/9H/CslPL2svJNwB1SyquEEPOAu4qHHMDPpZRffKFO2Y6BNq9XCoaJiWWTX799EN2Q1AY9tNdVkM7rTCXzrFkQRlUV4ukCT+y2Vqnb9m93MLinl1QkCRLe/qNPElo6F6eqgCGZ2jLE8OZBYkP72Hr/swC0drbSccZCrv+nG2hYUFRBS8k9z/XjcaqctaiWUIWlLUhOZbjzc48iJTTe0Inump24xpnTOfyTnUjTel94fApSauQyCkIIpvp2sHfDTgBaFrcx74zzefbOX5Uc/X4w+hMC4RBb7tnPs3ftA6BhQQ2B1U303r2f+jmVs9T3TQtrWH7hPDrOaEWo1lAaS+bZsHfUOr9TASnJ6ZLExt3s/92TtHUuRSvM5N5XnQp1c6soZHUaruxAd6qokSyHfr8LLWcSqAkSn7BWEFQdCrnEIUYPDeLxe/jQHR+lvqORx3+4nu+8/1uz7sXFH7ue6pUd7P/ZemRBJzIcYaRnmMqWMO//1W144gmO7B4i3NbO6ksXECw6B77cywTbjoE2fw4vqAmQUu4CTj/BsXcdZ98wcFXx82Fg5Yvr4msTQzf53defQghLvSqEQFFE6bP1H9besMxa6OQFvCCEgFdhXqdZHN29473u9j5xhO5Ng5iGiepUCV82H1l8yXucKnnNtO6ZgLqQlyUtlS97v/+SaLpJ3jDRdZPmah+xdIG8bpApGHQ0h+go+zro0CQAACAASURBVJKduobcsZ++7YeRhUIpEQ+At62BeEazUuxmCtBZS/PSWjI/GC6VGdg3wNjhMd737Q/PdEAIFCCnGRzoj7E47MfhdnDvfz1NJpHHF/YSUBXUgAfdtKIOjGicfNcoDkcaaZokJ0c5vGWAwf39NC1sYeGZp1HTWo9QBPNPn08ymiQ+vIfWzlb8VX4+9ftP4/S4SEymWfPGxcw7vYnn7t7HkZ0jjHZbnvwjvVGW3tBJejxDoLMWxeugbXFdSQAAqAy4ecOiOjYeGCczGuXgjx9g/6M7SUdTeANeWpfMTlNiaCZVl3WQymkkhQDdxFRM1n/3xwghWHnpGqoamzDMSnTN4OCze5kasvrzsWUf5ow3nomUkiXrOhk5OExsPEbTgib6n9nLI9+4q3SeM25cxxVfuRWzoZZhCVWNjXT43ARDVUSGE/gCbkwpcbrsjIA2rz7stMEvE9KU9O0Ze8Fy6962AvMkBnchX1BO+ItQbl9+IcrLTdc7uGmw5Mld2VCBTM+EhKkKGGUO5B7n6++leWAoTs+IZc+vDXpKnv3RlOW1jxCkphL873u+we5Hd1HIFkP/zls2qx3d6QRz9kMhdZP5y5dzsGU7U4NjXHLrZbz3Wx/C4ZhJlZsrGFQH3WQm0gzdf5BNe8cJhn0Eqn20n9mC44wmoroJyRwOVTDyq/U8/p37aFs2hyM7DgPQsbqDwf39VnupDIMHDmDqBg3zG+nZ2lM8k+Cmz/41C85ciMPtZGogzs4/dqPldbwVbga7JjANSds5bQTmVWGGPDg9DswaH3EpIaOxuXuSczrrZ82e66q8XLG6GWE08of3fJV0NMXyC9fQef7ZaAWFycEo0rQEbU/IQUozrAdr+voPDVj3Skp2/HEzgeoAq9/0JhTyNMxvpKYlTPemg5iGSSqSZO8GK1lRqL6S6z75boYPHGHTPY8DMOeMRVz8mVvQqkI4nCrZgoGRTLP9e3cTDARYcNZq9j/Th6GbaHmDd37xMjsqwOZVhy0EvMIof6GB7ujEKi9H+yfTrqHNZHA7WrNxrKbj5VOdvhIUcgUe+9yPSafzKIpC4cKVuJYvBMBVZj8O1ATwVHhLAsDRNCxpQytKjuW3zHE4xqFt4yy98Ao6VjdyxUfeAFiOgKYpOTgYp3skjpTgq3CRL0pchmYwdHASBMxdVmt53QHjDz3Hhu89gKEbePzu0nmO7DxCdVM14dZajuw4TGQ4QvuKdka6h1FUlZWXrOTDP/g7Qg1VSCl59jd72Hr/wVL92rYQNUtqqVnZQNKlkpQSNIOMZhD0Okhkdet5MiXdQ3HLGz+rsbIjjCIELqcDnPDFJ7/C3V97CN0IMXIoicuVYdu9D6Iogob5zVQ0VFLHbO1AZO+RWdvJSBKHkuW5u9eTnLKEM7ffTXVj9aybm5xMEhk1aVq8mDkjI9bgXxmiUPwSfG4Hsf19PHLbHcRGo1z1t7ewfX3PrHN1bx2yhQCbVx22EHAK8XIO/ifbtlk+1T+qlqmbsG0UoVozOX1BDSx8faRVTUwlUFSF0QODHHj2AAC+2hAtRSGgwuucFd9/46ffTmRwEkVVKWTz+EI+1r31coSiUDOvEc9wElSBs9JLTcBK/KM1BVh83RIAgmEfQ5Np/B4HVRUekJJ0XqO6wloZ0JSS5ovmkY1mqWsJMXE4gmmCI62jBxVy+w/xwGd/yrTPUN/uPsKttUwOTGDoBrVtdfRs6cbQDZac08m1n7iehvmNNC1pLc3ctbzOPV97kpFDkVn3YqI/zryL5pJ0K5hlajCJtWZBOOhmKpEnmi6UkhwBxLu3c8FVlnPd+JEoU0N5DCNUeoxM6SQVSdLa2crB5/Zz+W03leo6FNBNGNo+e2B2eV0c2LgZQ5tZrCifzjPSM4K/cibb3+qr1wGgCwdXf/OjJIzZOQ9GntrF7/7uOxjFBZccLg+Qm1UmUOXFxubVhi0EvI6IpfNs75l2rhLUBNysmFcD/OkCwMnOwcsXbDmZc7z5Xy4sVTB0k1Q0xzNdo0gpUHWDnm0zNm03wOUn3eVXJRt++QRzlrfzqbWfZNHZizjw7AFqmmuobKiclTxHPWp1wIaOBro3daMXByfVoXLuLe/A1E0i/QaR/gOE6vzkytPS+p0Ewz6SWY0MMNozxar5NVRWWCvhrV5Qy+BEil1HptBNa4iqvX4JwazBUNcEmJL0tmGEACOVwF/lR3WoKKqC6lC59DO3UNE5F5A4JJxvLT6AdLuIApPdo3z+ik9T3VRNw9I5LL72HCrPbqDqSis9rlsR5IqDvlAECGv1w6YqH0vaqhCK4KHbnyMZzaK6HeSyUwx3HUIUzz/n3OXsORLB6VBIRjPEswXaVjSgZ3VAIqXk/FuuRgiFjjNXUT9nLq6hBBUdNTjcDjTdoHZuExXBIFJa5WsWNuKurcTQdIaf2YupGZYDnyJweZysvvpsqpvbqJvTgpSWViX13CDBpXUIRRS1V5Ktj+5g8drFgMBf5WfR2e0U8iaJrIZpSnw+F8EF1STTeQJlWhUbm1caWwh4mRCq4MPfv/6Ex6c9hRV19st/Vkz30W0e3cZR2wGPk2R2ZkbjOYEjUiav88iOIdrrKphTFyDgc7KnN1JcUMZqt7OtclZq1hP1oVwAeD6TQDxdIK8ZBH0u3MV+JSfS/Oxf1pfKBMtSrE7XeXDbTPRpedvT6XSP5sBgjOFiLDnAguYQLWH/MeWm+36i/v7Pe/+blX97A6YiZpWs8DhJZjWK735kcb/HpXLhssZjvL8VRfDDT/yA/9j+TWrayrQaEqRpopTs1UfXU1AcKhSFAEM3CFS5S2l6AWpPa8ThcpApZupzOxTLgbSsnT137ye9tI5QnZ+GjjAttRW0hCvQDZP9gzErpbBHZd67V5F+9DAD+yZKdeeetoTdj24G4PJP/zXq4rlkTauv4YCLgi5JZDUoWLPfirZGtIKOt6mWBR+4vjTgoxk0+Fx4Kpw0eJ2MRLIk0gU8LhWfy0Gowo1QBLl0gcPbhjGKCYUU1aD7id00rJjH8O4jdL77Sg6PJWcurtpLOpJlamDGYbK6aa4VxmhA71OWQDn3XadjuK1nrvOvLuXg77tK5RtvWo0Qgnx3P3/84i+Yu3IuR3bOmAyu+dg7UBwBCnkT0zCZ6C+ea6vVdnVjgPhElO33bZqV8rjzvHWsunoZ9fOqebZrDEUI/BVu9FeDY4+NTRm2EPByoQgUx7HLnf4plAsDJ+N5f/QAdKLwz+lIgyNjKY6MpXAoAv0o78SFLSFcL2F/uwZijMayXLyyCTfq8S/iqO2JQ1O41h0/hbSjuNTt0eQ1wxqYihim+bz9Ol4bT//iCbb+/lniiRzL/+GmWcc0wywtTVtOKqez40iE04uaF7DszTvW72CkZ4RwW61146f7IkAqM0LW0X0TQnDbvZ/mV5/+aWm1v6m+Ls69+UpS0RzVnbUMIclpOt5shh3fu5dcIs3yj78V1evBmdcReYPUeJrDhREuvXXNzHkEOBwKbdVejoxag2pGM9DKntfathCBqoUlIWB4x2FqLzu7dNyUkMhqhANu2usrCPnd+D0OLhz+MU/sHqWgGeiagWswSfJwhM3dU3zwu9ehOlXmN4bY1j3B4FQGTTcJhzxEhhL07xqlcV51KVSw46pOlrz3DJJCYa0CLo+TbFmKZIBCdvb28b5Q0zlzXbIsY6HqUEq/GbMobEVHo7PqOpweIqPWQk0VVR68ATfZYurl+rlVjB2JIqXKJe97K0Yhxs6Ht3DWzRfhWdbAb774OJ3r5nDuzacxkcxzaDTJkhbbJ8Dm1YUtBLyOOHpMNU8QdiCOKnm0APDnnK98EDueiWD6v1qm+dDKtBZwrBBj5Ax8TpWMZhDyOa30tqYk5HdSVTGjUp2tPTlWszLN8a6y/Hg+neNn//wj/vi9BzF0g3OuWkVLayX7BmKlMmsW1OJzWz+b6cH52a5xcprBnNrZK8ZtuW8zfXv6uPkbHySd01EUgSIEQgEFgeMFhMQl65Zyzcevwxf0sfG3z9C36wir37ikdLwhmmXjgXF2/uB+Nt/5JABatsDS8y9monemz8vOm4uj2OfJ/hiRkSROt8qOh3rwtoXQWgOYDpXgnEpGd43RsijMcM8UUjp548ffyTO/foDVt1yMq5hdECBdXMzH41JpCleU7qUQggtWNhGdSHHPV58kOWlpZVSnglqmWaqv8jE4lSGvm4xMpdn24+0MH7QG/3BLkNrVTeSbAiVtQs6EXEbD3RdHi2TRsxqK20FVQ4BQja/0zDlcKr6ge+a7FlBTadni9UiWbLpAU0cN6XiUXGpGqzAdutva2Urz4hbyqRy5jEYyNiPwpaKWjd+6P2MkxgdQFes+5FICXzDIN/ffTu94kl29UZa/+3S6frWHRWe20LK8Aa9LJVvQcTkUPC771Wvz6sB+El/HJLMamw+Mkx2ZYuPX7yxpBixnbBOhCBRV5bwv3lqqI4E1HWF8bnXWALnz8BSpnAYSFrdVUhPwHFc70T0cZyKWQyLxuRyk8tZLMpmxVKVd/TEyBZ1FzSG03IwQ4A64aHnLUloQoOmMP9CDUAXuWI7K1iCrOmpRyuzmRwsd0/saqr24nJbDmW5KKryz9RnHqzeNy+fmgW/fV9o+/50XIxSFlrAfw5QoCridKooye/C+aGUTzuIgUs7ya84iuXAeI7pkZKelPg75nMQzGk5VobPNytaXyBSYjOdKORJaavxUBtwIVeGM694AwNKLVh6nx2Cmcxx4bGdpu2vDHuaddjpWXi+L7i2DnPP2FTjdDjKxHA/dvmmmga4JnB4Hbasb0bIGjR3VCEXBLK5JEJ+ESz72DsymenIFg0xex50uEN3Qh+pSSc2tho7wMdd+aOMAyTKzDEB6KkNFccBuCftxOgSHRhLMqQ/Q+ol1/OLf1qM6VISqUAi5jyucdj/UXboNLq8Dl8dJqiwDYrDGR6LsvK6AC7W4lLJnOEH3E73Fvhzg4KZ9vOkd5wMgVJWWJS3sfmx3qe71n/ogDrcHp0slMTlOOm5i6A4GD07iq9B58Nu/ndW30y47HRSBWjTxpL0Orv3H89CmMrgUQX3Iw96BOAVd0hq2X702rw7sJ/HlQsrnda4rny0fzXFV/SexzywO8tMDjWZIxqJZKnJ5tt5//AyMDqeD5bfNDkVTHQrToWXTxDMFoimrXCqjUR3wHNOWxArlml6/XhH5osObKAkg6bzGVLJAPD2Bqigs+Jsz8TpUMrpBUrdUtaohS+le557WyJqFdcc91/H2hUNewqHZXtgnundHt2Ee5fE9rb73uJ//ZyJMiVYwME2JYZhIU2KYkucOR8iVGYEFoBXV0ZphsvNIhOGpNFPJ/KxcESG/yxICjunj7Ct5+L/vYqJ/guaFzbQsaqaQ1RjYP4DL46dhnpdULEsqkiWf0dCyGk63g9bl9Xzou9ex8Zlekn4nXgTxTUMcenpgVtvNi2bS7EZ6Eyw9V2FSN1ELBhMPHyZWVJFHhxO0L6unZWldmX8DrLl2CQvPauUntz2EEIKGuVWM90URFS52HppkWXs11QEP9ZXFWbyqsHjtnFI2wcJDOo2XzKN1US0ep0p8eJI//udvMVMaVY311LbPR3UoREdTCAUq6ypwutVZAgBMP8sWsuwmTw2OkZxMcPg7vwOHA1WxkitNc8mtb2ZqOAtkrTUHnlnP2OFR1t54Mc1nnIanyUXormriIzORD7sf3UV8aIq25hpaa/xFHw0B86qtb08IOltDRFIFsgUd719AG7B169Y6h8NxB7CMcsnQ5lTBBPboun7r6tWrx49XwBYCXi6EOKFNfpqTVcKfyCfgmH2K4IwFYeqrfNy/qR8TWNtZj5davn2Ctstf3M+HUjbT23EkQt9EinDQg6bPHjgLZTZXU1KcUUqmrQDT7WiGRDMsVavf46CQK2un7L5Fh8scwZ6H53PyO5HAcHS9fDrHFzZ8BWma/PDj3wcJWkHHNEycHucsTUQ5Xc8N8ND3Ns/aF6rz07imGbcpKUxlUJfWUlXrR0pBpixXgmHKY5JFKeLY8xwvAuPpnz7GeO/shFQdaxYSGbEGL0UV1DQF8QZcPPKDrVzy/jPwBtwoToWY14FpSDQknjObcR+YIJ+cEQaHDkyy+KqFONorSZomk7pJOKuz7Sc7ZnWitjnEH7+3mdo5IeaubKS1s46qot07WF/BOTcu5/DWYYYOTpFNFag7HCHTEuSxXSMsag6xqLWSwaE4j/73M1SWmVPiYyneEPDQ0WpljEwoBpt+Za07dsa155PPWwJKMOxDCEG0KJQ0zKtCrzQxdAOtYOAKzGiCZMF6xqQ0GekZxuV1ER+YYO8Ga0HUlsUtFHJaMQogBFjfk8uVZuywla74mTsfYWk6wem3vYO3/Ow2Jncd4t5/+gFaroDT42Lnw70ko10kJjMEKnM8d/cGssks1/ztG1l7y8WkcjoNlV6yhdmmsJcLh8NxR0NDw5La2tqooii2W+IphmmaYmJionN0dPQO4NrjlbGFgNcRQggaayxP+KvPakPTTVxOFVM3eMdX332iShhH5TTXdRP3UZEBRw9M0VQBVQgmi05S04QDs8OfPGNpojtHS17rkWofGCbKqkacGQ2R0SgIKztgrtqawatls+ep4QQvBYZukphIoagKobqZwaZcGPCG/Cxca9ncv7z5G2h5nXu//hRDB6e4+UuXUdkYOH7bR2sQgIpKL13FBDnB5iBzltYxkcjjUKzQTVOaFDSJQ1WoC3qY2xCwkvpIS2B6rmucyoJJdihBfDzN2puW4yzTSGQSGWpbw/hDPvr39ZeSMJVrM0xDEh1PEh2zPo8dmmLuaU2AoNbtIN4XB1UgnOosJ9a69kqWXrWQQ4ZZSuF47tIGKitcNFf7qJ1ThafChaEbPHfXPoa6pxjYN8HAvglqmoPc9PlLrPtiSg4+O8B4n+WfMDWUIDkZpXp1GPeKDkajGbJ5nUQsRzqZJzGRoWlBDVNDCfIZjUxGYzKWxaEK7vvPmTS9TW9YQOOKxfQ/fAhf0ENicmb2P72o0TS1ATfnL28EwLmikYtvXIZpmNz0mXP5wpWfKQkAAINdg/grK7j0/TcRGdHwVriobg6i5SpwelxouQK+UAV1dY04chpZj5uKMzq55bGvoyTTZHZOsuvx3lJ7XU9tLeWESCezPLR9CAmsmlfDvPoApilPKFi+hCyzBYBTF0VRZG1tbXx0dHTZicrYQsDrFCFEKcRPcahc8/cnDle897m+0mw0HHTTN57CcVTootupUBu0Bnj5JyQPlnmdibJlcPOJPPlMgfyOkVlN1M2vxnPJPKtO2anj49YCL+W9MU2TjXfuYd+TvZz39pUsXDvnmDLlHNjYx9Rggq33H8Tjd3LOW5ZT21ZJbXvV8/Z9/e2bGDpo5ZKP5gpkp9IlYclygrM+JVMzM+ia5gCKolgZ+Iq0XTqfuFsl6HXiciqksgWcDgfpvE46rzO/IUB99Ux4ZC6vMxabZPCJPka6JnC4VM5524qZeyolW37/LHuftFLaeio8LD1/OfVnLGbe4kUoGuwt2r7r26sYORShfWUD7ac1WbdcQFXWYPPd+wFwOpJs+v16VKfK4nOWct0nP4bL66IunmN7zyTnLW8smUTaVzaWvjbFoXDmdZ3seeIIejFiIpvMc2Q4weGxJNmCQcWCaiLDkxiFKIe27GaoawDHjx3c+P2PI1Z20D+ZRlUErWe30fv4EYa7p1BUQaihgrGAk96ucVRFkFJmXlV15y4nhULtjUvxxXJkchqJ4SQur4PCUc6moVo/If+xcS7xvXFGekZm7bvwXW/C7a/D4fLR1KEwcjhaMomc+7arycTiVLd0kIrl6f/FHuae3YJoCpALeXAKJ1pSo669kob51Zi6ZKd2hEBNkEB1BTXzm5jWxWYLlpOoYZr8BbJiKrYAcGpT/P5PqPK1hYCXixfwCQA4OBQ7JlWuIkTJtj+NU1HQzNmzzXDQTU3w2Axkf9avvWxMT+d0JhP5Y4pU+V2zsrcB1ASOTXqiKoLqCtdMHH14dux/YjJDMOwjn5n9sjY1A/3JfkCiGZL69iqEYgkzqUiGiuIgKU3JnV94nPGiYOEo2lVnhdcdtX3wuUF6d1rq3Fxa45EfbuOKD5xJ+HmEAEM3SzNYgJ6pDNmx1HHLulMz98vQTHSZJjG61/JRmNOOWeGC6dDFrCVQJctC247W/ueLs3qjqDI2NIO9jx3itMut7ILpSIpvvfsbpfK5VA5NVam/+hxWLa2nKuDh7OuX4vI52bW+h8mBBKlIlmwqj7cYVdEw37JTOxwpnvjZPegFnaXnL+M933w/Lp/lj+BQFar8Lu76/GNIKWleVMuSdXOoK9q4AVxeJ+HmIKO9URZetRC1vZL+yTSZYu4AZWENiYeG2fLzx0p1dE1nYs8R1A4r/NMwJf6lYUJ7xkhEMqAIWt/cSaJoajJMSeObzuP8vMYT37ybabWSYUqSQTctf9XJ0N370ZP5Y4QAt985a3vfE3sI1YXY8+iu0r5guJKL3vNWIiM5MokCmUSBlkVh3F4Hpinx+F0Ew2Em+mOkYtZ3rRcMIocixJ4ZwF/tJVEUVgGu/JuzCYT9XPCuVTx751Oc9ea1CEVBSsn2nklCPlfpuvKaXoo2eTUgpVQNSZVEOgVCUwVRIcSxMbE2rxtePU/f642T8Ak4NJKkcJRNPeB1zhogwBpsp45Su58+v4ZqXpjp8aUULiiODcWr8rtnBp4/IVzweNdnSEmkbGbsqHDRedNy9v1yN4oq+MC331TKFieEIDWV4Uf/+CC5ZJ74WArtODH4U/3xkhAgFEH78oaSEOB0H5vQqLxXhmYwXLZE7TRO7+xHP1fQ2dcXRVUEsaKTXLnX+cngdOsc3PgEh7d1lyWO2UL1ufNxz2+Z6VPZPQ54HSxurZoluOzrt64t1FFDajLDwrNbmb+mpXTcXxPgnf/xXn70iR8AoDodrP34DeSh5GzmDVmOmxXVXrS8zuRAnKd/votL33+GdbzKy7KPnk3v/RvRi8LGh77/USobrL4ksxrPHRinoJuEz2zhwF37iAwn2f3YYVqX1HLeO05nXDMYmkrju7yDBYogp5soukm114WS1TCBgiE5/b1XzxICQo3VdK3fRlP7AhSXE1MzSaYLxCczNM6vRgLnrZq5X1Z2P/jV7xWWnLMEl6HhCwYwrWxNaPEs9Z019G7YhUO1fk9CVTA1ncE9GdbfPobD5eDpXz3Jvg17CdYGWXjWIiuz4bwG2k8/i8jI/2PvvMPjqM79/5mZ7UWr3rtsSZbkhjsYGxub7tATyqUmlB8EkgAh5SYhAZJ7E4rhOpAQQgihhBZwQmgGA8Y2tjHGXbKa1XvdXmfm98esVruSbEwS7s3N9fs8fkCzZ86cOTs75z3v+32/30SKX783RMCr/Q7T8xx0HhrEnmrGnmZhsENLUQmCQFFNFiabgaaoA2KyGug62M+Mk4tRRYHFFy+N9WmURE6szIr9LQgCBt0/jz5GWFGyZZUcYrtGlYhKgSSoPXpR7P0fHdxx+8LsuBPwP2gTqWKBY9YLPlKzI7EKPnrdqwno6LHGAlB6xRzC0UXRYdF2TnpJwBW3q7KZ9Rj0EqKgbcTEKJZgzMSIjK7dRchuJHSgH+uSfMImHRFFRUoykj8ri6UX1CBN1Kk3addVJIHp182HIR9NfzlEyDvuCA0GwlidfpJtRg62DPPJ6xrjW15FOkar4aishaP+MDlXz0FAQBSAhiG6dnYiGaUEfffmbhedUWR50ox08ITILErGNegj4A1h0UsYozs2RVERBDj05Jt07m5CjShkFuXiyC3GlZbGzJUpWFOSGB0aIm9JBZml2ehsxii7oBAbaUV+EhnJFkJhGac3ghKOUPdmE0GHAWNYZsGKUk6/dFyJO/4+T/9/ZzLYoUPSCziKkgjaNJyDyxdCpxPRRQGfHQfHAcGqqsbmRq+XKMt34Fm1gPPsZhpe3cyvr1/HLU/dhi0rhW11fTEHVchL5D8YGvCwu8cJgoBeEjHZpFj0yGrSM+gKYDXpkGWFQFjBmmLljh0P4/SEEAx6AqJEllGPcniE3W83EAmNO8KeET/uYT8HNzajoqLTS0gGCUkSqf+onkNb61gq6nBGoy9SWKbvT3X4nAFSs9NZf99vAE2EyT00Diq1p9kxWU1EwhGySrLZ/uo2AIa7hxntdzJ98UpUdTxiqo9D7ouSgKoqeEe9hP0eVNWOIAjYUy201/WTXZzCsotnIuklFFlh1opSIqqaIPKoE4RJuBpVVdHr/jmUMqMOQN4UH4mySh6Kwt/iCNTX1xtmz55dU1xcHDh06FDtxM83b95s+epXv1ocCATElStXOn/3u991TCzBBfje976X/eyzz6aLosgDDzzQfuGFF7qOdr7f7xcuuuiikv3791uSk5MjL7300uGKiooQwLp169Luv//+HIA77rij55Zbbhk61vt55plnku++++5cURTR6XTqAw880HH66adPChF+6UtfKtm3b59Vr9erc+bM8T7zzDNtRqNR3b17t+maa64prq2ttXz3u9/tuvvuu/sAfD6fsGjRospQKCTIsiysWbNmZO3atd0At912W+4zzzyTnpqaGgH4yU9+0vWVr3zF+dZbb9luvvnmIlEUaWxsPHis9zDRPtMJEATBBHyIRuWuA15WVfUuQRB+DFwHjPGMfl9V1TemOP8M4GFAAn6rqup//q2D/VeziXl3OPZw/qQF/XOeP9Z4YqnimLTtigUF6CSt3l5RVBq2tlE2I4OkOFBde5+bnmFttyzJKk3vNMc+W31hFVJcLnburByS4v6WFZWOfjdud5DsWdmknlykyb4mGZlz8yIWlGfQXT+II9OGXyew/VA/OklERAO5LT6vilmrp2Gw6BPu2RcIo9NJ6KNAN0VWo1TI2ks5aVYWWdNS+XTUT8PebjyBqRlgzgAAIABJREFUyCTHwSUr6Dtd9LeNojfqMJh0uEYDhCdEHQaauqjfov32vKMe9OYczMklsc8XXnMSgbwknACe4CRmxsZuF5IosrWuD1UFs06k/t1xgZv8ZDOp2YlgxLGzFUXAPaztXp39fqalJqErT6Ox20W4Y5RZxamk2I2YFuVTVJOBTZIoiIah+1tGaPq4A5/NgJxqQp/q4NDWQyiKwiM3/JJV992INerA6UWRiUmTkiWFuKMRGwEVh6ylRNQBP2RbwWbAG4hg1IvMyHdQlutAFGDboX4GnAF0AsyblYM4J5fZq8p4/icb8QwHMFn1eJ3aPW16bi/ZJSn0RiM+JpuB0V6tbHTDtx7l5LVf1+Zhfz++6DnxeRX3kJucaTmxvL97yI3ZbqZ6eQ0HN42DAQG6G7qYvqAPxByMNpG0nJSEVFDthx/S+HEtIX+IcDDMl++6BddQkJZ9vZjtRhRF5d0/7Aag+uRiUmZmUZBujWoLqNGhTXYAIrLKPwNnUDQFkHO0NrJKjk5V+wVBmIyC/QwrKCgITuUAANx0001Fjz76aNvKlSu9p5xyyvSXX3456ctf/nICGnjXrl2mV155JbW+vv5gW1ubfvXq1eXnnnvuAZ1Od8TzH3744XSHwxFpb28/8Jvf/Cbltttuy3/99dcP9/X1ST//+c9zd+3aVSuKInPnzq265JJLRjMyMo4p5bFmzRrXZZddNiqKIjt27DBfcsklpS0tLZMW4Msvv3x4/fr1LQDnnntuyUMPPZT+ne98ZyAzMzPy8MMPt7/88ssJPyuTyaRu2bKl3uFwKMFgUFiwYEHFxo0bnaeeeqoX4MYbb+wbcxjG7IwzzvC88cYbjeecc870Yxn7kexYHsEgsFJVVY8gCHpgiyAIb0Y/W6uq6v1HOlEQBAl4BFgNdAI7BUH4i6qqUz4Q/1J2DDv6/HQr/pA8vggLcfn/uNNNepEkS2JuUycJDLsSQ5iiKJBs+xvESY4yVlEUEEWB5k+6aNjezqlfW4B9Asc/gDIhBjG9LC2h/FBA26WOeIIMOoMMuQOYDTqcviCGJfl4ouprWQ4z1cUpCJJIXpXGD+Ab8aGoEIooSKLAxT85lcy8JIS4/odcAQZGA7j8IVzeECfVZKOqaoxXH8CgE3DF0c6OIbOnunspmksOR8mOppqj+B1L16EOps0fASF9fE4mEMWLokB8PaA/KLPtUH+sa2nCQuHqT6x5jzeN6EmIOTneAS+6Igfe6Hi3H+pnUWUmg9Edszsi0+IJsO+nHxD0hxnq0t6zJUvz+euPfoMSxZzM+epZ9EcX1bPmF8RYDWt+uQZBENDpRUSdiC8YwWTQ4Rn28YdvvxUblzXZRN6qUgJpFkx6iWl5jtgCWJ6bRF/XMJGObnZ2dJKan48k6Zi9chrNtf048u2MdLsJe0OEfOGokA+oCphtEr3Nmo5E2856ku97noWXnElHwxCOdD2eUT+RoOaQmu1WkrNSySzOoK+lL1YxYXVYMZgMnHzZmSRnZaEoRrzOIMkZEuvvewJrsg2f08ui85ZhTc1Br1eJRAyE/AG8o1rOf9aqOVrlglObV787iDnJSPX187Sdvl7CFxx/5qZa/H0hmd4RHylW4z8FHkBWSeGzOQREWSVFJ3DMu+bPsra2Nr3H4xFXrVrlBbj88suH1q9fnzLRCXj55ZeTL7jggmGz2axWVlaGioqKgh988IF1+vTpoSOd/9e//jX5xz/+cTfANddcM/Kd73ynUFEU1q9f71i2bJkrKytLBli2bJnrlVdecdxwww3DE8c3lTkcjpgT5Ha7xYnf75h95StfiQlazJ8/39vZ2WkAyMvLi+Tl5UX+/Oc/J8e3F0Ux1ncoFBIikYhwpL7/0faZT6CqubJj4Q599N+xbjgXAk2qqh4GEATheeBc4F/fCRgj6D+KTc9LnnTsWDkBAsEIG3Z3JRyzGnWcOjePgMfPU7f9Nnbc4rDg6u2JdTZGrSvqJHR6PSFfOUxwMialFVSVzkODrP/Fh1zw/VOwOiaQBcW9QmadWpZQ+qSqKlsP9jHsDSZMiT8kk2434g6EY9dLTTJgNSeOJUpeh9EZZGZpGlmF4/PWNejlYPsIwbCc0PdHe7rRe0IEZAUjIPgjGAwSoeTxcQuAzaTDE2UutBp1hGWFmqJk9n+aiBw3JhkRE75PgXk/uJKU8nzeX/sK8885EVGfjByHS9OZdMRDKaeq/4/HBwgTn5fP8Q4w2AzEb9EiikrzhPLKUDBCd1PiO9zZPICzb3zXa8pOwwckWw0JtMbGCQh7i0mPANhTLVx2zyr+eNe7qAp4RwP0bmoj5dwK5paloygqg/1OdvzxfXas307jR3WxPs685Xq8UaBdxox0/CUpmEpSMAFiWKHj6T2oihaO9wz1MmvVIva9u4PSuZXkF9dQ/0YjADq9mw9+/yrJ2amcfevlSHo7o/2aQ3DWLTN55/EXKZ1TTN3WOtjbwilX/xvOQZUxqV+/W3u9eUe1/25/dROpuakMdw8jSiJmuxmjxUh6QTpNHzew7909zDltMQU1c7CnWkjKd+AeqxoJy5RlJ025+I9F3axGHWXZSUf9Pv87TUXVf3arsXb/uIWpra1Nn5OTE/PKi4qKQj09PZPG0tXVZVi8eHEs5J6bmxvq6OgwGAwG9Ujn9/X1GUpKSkIAer0em80m9/X16bq6uvT5+fmxn2VeXl6oq6vrmO5/zP7whz8k33XXXXnDw8P6P/3pT41HaxsMBoUXXngh7cEHH+w4WjuASCRCTU1NVXt7u/Gqq67qX7lyZQxt+sQTT2Q+//zzabNnz/Y9+uijHccauTgWOyY3NLqj3wVMAx5RVXWHIAhnAl8XBOFK4BPgdlVVRyacmgfE33wnsOjvH/a/rh1zOmCKlp7+UX565mN4Rzw0fZKom14yp4SWPS1T9lV8yTJ0HNtLydnvpWFrGzWnlsWO6RQ1IRKQnpeUEJp1eUMMeSZXHCRbDXgCEYJxBEOjEyoQQKvDFyMywcYhXnlhP+n5DiSdqJHh1GQRyEpUCTT4QvS828JoTyLRUPkFM1AVTSbW6AoytLkN06I8dGY9aXYjiyqzYjiB/JsW89QdrzHSdZiaf1uFP6JMCZpUFZUv3X4ltpR0tG2r1iYSDtH0zkcIFs1nrlo+k+qTq1FVcI/6ObSpFV22lYz8JNQxt0xWuOj7pxAIy7Q5/QgWPXuaBzW1ySh+A0Frq8gqudPTtesJAqI/gqHNmciKp/Qx2HgYQRQ1508VEIURQCAlJxujzY53qJ+V152HtTQNQSfiqWthtHuYpTedpTl+g9ESTVUr3ZSiWBCHWY/DZkQUBdLyHVz6k1WM9ngQdZpwVtCq52DbCKAy4AoinHQCK5bM5vDKO5CjYj3xkZKBukFmLi/BOcYs+X5LTE1QkVUEIYXyVdOYd9Vq2t7viqVCACIBjdVvpGeI1//rWU699nyIPs+WvGyu3fALtv7iuVh7JTwM4riio9E6ObLlyHAw3D2MImtYgEUXnYg+IxXlw/34DraxZ8N29mzYznl33oB+UE/FycWIkkiKJNLXPMxIr5uKhfkYog6tIExUtfjnMQEhfCxvHq3dP86mAhZPtfs9Qjv1aOf/Leccq1155ZWjV1555eibb75p+9GPfpS3atWqhiO1veqqqwoXL17sOeOMM6YuLYoznU7HoUOHagcHB6Wzzz67bOfOnaYFCxYEvvWtb/X/4he/6BYEgW9+85t5N910U8FLL73U+rkGfbTrHksjVVVlYI4gCMnAq4Ig1AC/Au5Be3ruAR4Arp1w6lSzO+XTJgjC9cD1AIWFUyvHHbdxU6bIzMnBEHvf2YMlyUL1smoOfjieqrIkWRElMYFMJqsiH73JgMlqRKdPjAYqqkp8BtySYSVtehqCKNBwoJfDDYOYk4yYbQZmn1hEeq6djiQTqXlJsRdeLH99hPeLLxhJqI4w6kXmT8+Y1K713cO0bhh3uAfjpGMtBQ6IcwJMQ34O/+UQOoue1NIUlIiCDgGDSUf7280kZVkJ+SPIIRn3kI+y+bmEZZUhd5D9LcPMLNFqLhRZpuvgp+x5Zxf5CyoxHLZrC/BYZYMoojfp8Pc4MWea6Dk8Elu0APSGMO898ZfY3xkOE6lnz9PGqKj89f3DFMzNQbAaIA4bsiRKbOPe0UF37QB9xY6pX1KKSuehgYRD1mQT3tHxxVGJ9LLrrxpj3UQ7/caLGOoOkVGQiiklE8PSYgRBoOGx9TS+u5urf3Axsqywa28n6CREkxYJSLcbGXQHMepFqgpTKMiwIQCp+Q5So0yB/mCErQf78IUiZCRpkRdVBZ8gsfL2C3nnP18AJpMsDbzfgr0oGSHFNAl/IUcU+g8OkL5iEZENiZsqvzvR2fOOujBYNScgY34uThUW3XoB+/68jeSsZFp276eguhydSatAGO1XOPuWy3l93bOxPoQJTJr739vPBX/5GcWXn44ZjUTp9ZsfBgRa9vYy2ushOdOGNdnEgc2t2rg8QRacWaE9M//EJgmMRFQKOHpKQJEEJm7y/i4rLi4Ox+/829raDNnZ2ZMcjfz8/FBHR0csFNXd3W3Iz88PH+387OzsUEtLi6GsrCwcDofxeDxSZmamnJ+fH960aVMMaNPV1WVYvnz5sdGSTrAzzzzT87Wvfc3Y09Ojy8nJmUQBefvtt+cMDg7q3n777eapzj+Spaeny0uXLnW/9tprjgULFgQKCgpifX/9618f+HsxABPtcyWkVFUdFQThA+CMeCyAIAiPA3+d4pROoCDu73yg+wh9/wb4DcD8+fP/15NbTOVxRmSFiKxoi2Q0WyAKQgzEdsS+0JjgInGLTMgfATkxEy9EF1SfyzeeywYqFlcQCYXJLMqkv60/5gis+f13CcgqEUjoG7QSsVT7+IvYujAPnzMtoU0k+s+nExBFkaLZ49gijz+Mxx9GEEjIkcbbRD6EaTmTw6gAvtEjl+rpbQZEnYiKiugJw4CX9AIHyaUpRLJthCx6JFHAKggE32mmv3FCSjN6vYisMugav44cUdjzzi4ARpv7CfgSHXmdUSKrKAWztQivU6Fsfjate3uR9CJLvzyLgMvNxifG2//5/le54N81WeJ3Htcohjt29zCjPA0hexxsGZEVdJLI4MF+Gje3Mv20aQQLHZj6PLibhjFY9Sy/eh7NDQMMTVj0J4JFwz7flA6AwWzkoh+ey7P/vjGm0TCtMIm6zdv4+OmN3PjrmwHwO728fuXPcPaPMn3JDBb8+FrC0bRRMKzQ0DlKOCLjC0SYlpeMySDhD0Z4Jy5NFf8dG51BUh1xOhDqKKLOHosIKIEIjRu0CJZOGmSwpV07X1FRVRXJoCMgz6fmpoXa+HZ14+n3IodnkTejOtqnymBHM56BQ1hSbTiVmYAR14CT1NxUfC4fo32jdNV3UbG4kvrth2LDqV5Ww8EPD2C0GOk42J4wZyWLKmP/70cESeTsB/4fo5vbsTiMeEb9jER5JAxmHfNOK6d0ds4/vQMAIAiCLAlqzxGqAwCQBHr+FlDg0ayoqChstVqVjRs3WlesWOF99tln026++eZJ/PYXXnjh6OWXX176ox/9qK+trU3f2tpqOuWUU7w6nY4jnX/22WeP/u53v0tbtWqV98knn0xZsmSJWxRFzjvvPOfdd9+dNzAwIAFs2rQpae3atZ0AN998c96iRYu8V1555ejEMYzZgQMHjFVVVUFRFNmyZYslHA4LWVlZk15wDz74YPp7773n2Lx5c70kfXYFSHd3t85gMKjp6emyx+MRPvjgg6Q77rijF7S0SVFRURjg+eefT66oqPh8tcufYcdSHZABhKMOgBlYBfxcEIQcVVXHEqfnAwemOH0nMF0QhBKgC7gEuOwfM/R/bhOmwAR0DnjY15roTM8pTaMwM7EEayoMQE/9IK/etznhWGqOneG4kLc9dTzf3bCjnrJ50/C7/bTubyXoDSKKWp31GNPckTytdLsRu1mf4GBU5jsQEehzjj9/ep2IQRInAdpG3AEOdThjQkIOfwRDlwshw0rQMQ5clJpGEBU1qskL3R0u9JUZFM7MTuhv9fULWHnNPDzDPhRZ1YRyupy4kox4wzJEnR97RI0x5fU2D1O2rBjKU5ERcaGScXoZthw7bR9qbSS9SDiOHjkiq8iKgiSKHP5kPPKw8YlXOOd7NxMJKSiygiormKNhXiWiYLDoOPOmxVrYWtSiBaqictKXl7L1xS3aXMdNdnccmyCyGsMkAAyM+slJszLap32vjRuaEh6I2avKSLIamDs3jzmzc/n9bS/jGdUUDCdKRzsHJ2/cdHodj3f+HnOSlbzydDrqtGhC29Z2MtMKESWJBecuBmD/u3tw9mvvw8ZtdWS/tJHkm87DYtCqLyRJ5EDbKGl2I5sP9CAKYHAGwTT+Whn1hjB5Qzh3dNHeOERK1jjBlclqw2BOIugLEfCGGOwcj/C4h0ap3TKu6AegN+qZE4oQ0Wn96yvScR7sJ+BJTCEZLansev0j/G4/h3cdYuldV7HroZcZ7h7Hfimywmj/SEJkrHbzQcrmTaN5VxOzVs5moL0fs92sZVxCoYSSUgA12c6p1yxiqMOJ3qRVkeiNOjKLknEOeEmPRkb+N5heFHtRFBJ5AgAtAvCF8QQ8+uijbV/96ldLAoGAsGLFCtfFF1/sBHj22WcdO3futD700EPd8+fPD5x33nnD5eXl1ZIk8eCDD7bpos/Akc7/xje+MXjhhReWFBYW1jgcDvmFF15oBsjKypK//e1vd8+bN28GwJ133tk9BhKsra01n3/++Ud0AAD++Mc/przwwgtpOp1ONZlMytNPP314DCC8fPnyaU899VRbcXFx+M477yzKyckJzp8/fwbAOeecM3L//ff3tLe36xYsWFDl9XolQRDUxx57LKuuru5AR0eH/uqrry6RZRlVVYVzzz13+NJLLx27l/za2lozaFGRJ598su0f+R0In0VoIwjCLOAptBI/EXhRVdW7BUF4GpiD9npqBW5QVbVHEIRctFLAs6LnnwU8FD3/d6qq/vSzBjV//nz1k0+mVr3732KyqhKe8FJu7XVNdgJKUinMSiwDm8oJ6DzYx/r7tyQcm+gEmJN0HP74Qxp3agvYxFrp9LIc1jx0E7qUJML9I+hy0xNU7sZsTNhlorX1utnbqr1IdaLImQvyE3KdMbKbthGaesZBaYbGYZreO0xaoQN7bhIGq56wL0zLtslYmVkrS1l2xdzPnI/DPS4OtCXOpXUkQN2Lib5oemEyjlOKCMdys2CTRLwHBmj9sIXSk4qIVI+nIGZmmencepDu+i4+fWMnLXs1HMVVW9fFAIoASZLIgUc1SV57moXTb1xIOBDRAGCKCqJAW3sX+zbspuG9PQw0dnHfrocpqC7E5wpo5ZkqdDt91MZ9hwvLM8hOtdC2t4dwIIIcUcb/hSPMWj09oeriqpSvUDyrjJzyckQJ5IiMINqIhHXkzknFPjePHQ//iX1/3kbNKTP51vN3Yk/XFqctz+1hsN0Zm2S9QcLZ78KSbEVn0CFJPv760PMAZJflcM63zsWZn4sryvY4plY5RmZl6vPSsL6O7PJ0LIvziJj1mEYCtPy5LkYCpdOL7HnrNYa7B1l1/VcJ+SPoDBK509IIeIOM9GqEUbZ0GHH24hvx4B/14Bt2EwmGOPXxO7EadQTCMilWI4onyN7HJr8rug9tp2mnxttfc8pMXANO2ifs7mcsraJuSyJGOTU3lfzKfPbFMQqO2YWPfRNzdRk6UaAiz0F5rmPKKF7AG8Jo0X/uXPPfa4Ig7FJVdX78sb1797bOnj17MlvWEUxVVXEKxsC/OQJQX19vOOecc6b/PXXs/122dOnS6Vu2bDkq0O+fzY51fvfu3Zs+e/bs4qk+O5bqgH3A3CmOX3GE9t3AWXF/vwFM4g/4v2jHyAN0zOdOfMmEAwEadzZSs7yGoC/I4d2HATBaTISDYU7/yZWEU5IJA2SmYTXosFlEBl1BIr1DRNxefG19fDQ0QsXa6yZd76N16/n0rU+4+K7LmHf2gtjiH4oofFQ7vlGYKD7Uu1sLGA21OzGZ9AwcGkgIY4+ZTudmw6+e473fvRi9Qe0er3rgaxTPKZ04I5PnaAL7os4gIgrg/agTy9ICzMkmREHQ+AYqU/C+8ha1bzWj325BZ9RjSrKw0elhw2NvxvoYCxkbImH80jiIuPXt7ajhPgR9FvYUMx/84dMYkxyALc1M6kXVFF66msJLVyMJEEnWRILM0Ty53x1k/x/34x72s+CKOVgzx3nu41MrR7NwIEzd1gPUbR13fq77y0/RWywEbQb8EYWl37+cy//9yxTPLU14ZrJKU9kT5XZIy7WjWPQM9/oZ7o1yP0guBtq1SIHRYiSrLIfq0my21fXhHfXj3t1LxBNEXJzP0tm5dPo6aBaG6T00QP/bGzjxaxfh7XAmsEBGwgoLzz8HUQrjGdGciUhIpr22H51B5JIfr2KwfRRLppVP+hJTtaKgsWcOuDSJ6jGwad78XDp3dmEw+hjt6SLg8WEwSgiigMVuoWXPYYK+IBVLKqnfpoX/kzOTsSSZKTuhjIA3SFe9Vn443D1MVmliJGrM9j/1Npf+/ttUF6dh0h85xGuaQqvgf4sJgqBoZYD/GAdGkiTV7XZLlZWVVUfiCvhnsf9tDsBbb71lu/XWWwtTUlL+LknK//ki1f9DNpUPcMzVAMfiQUTbHNh0gOLZJVSeOANRkiidvwBF1qGTDMTXlTh9YeSeAd7+5iOM9o6HRquWVU/Z/VB7P217W3jnV69T+95eJL2OuWfMI39uWYxkCEAQwthHgzREBWrCCXXTTOkAAMjhIA0f1086HvJr4d4RdwBZ0bD0nj4PRldQ61AAnVmPw6QjqyQFUdTC43JEoS8aeZm1tIDROAZExe1n1xvbE65TOrc05jiNWcOOeqpOrsLV2Im+sgTFG8Czv5FdT79LX3MP5915A91NQ2QWJkZOJmLBZRV6G4bp3qJFP/QmHaqixgRqyspSMVg+3+KhyDKR8OTffyTdgV9RY2mS1BQrJaWJeA4ByK3IQG/UkVWcTHfzEI50K4XVmQR9Gli8bEE1F/37WXhG3FGVQhUEWFKVxZ//YxN9UcW+s29ajNFqwH5iEfee9hoAy684h6aN2lw6Mq04+72oqorIIO//fiOVi2eQXlROOKylBwQRLvzucpJz7DG1RqvTjzcuVaKoWlkpJJZWps7OZN9rb3Pw3U/H708QqFhcgaiTqI2mv+q3HaJ6WQ0tew6jNxvY9cauWPucshxS89JQVRWj2cD8NSfTWdtMSnYykXAEZ7+TGx66junT4zANx+0zbdq0aeHe3t7JYZXj9nfbGWec4WloaPi7HavjTsAXZG17e/gwLjSdWpyMfn4utrh8qUFR2fzQR2yNLmQCAqn5SZzx/xKrKAWgsCaLm5+4IHasq66De8+8S8O1RVHrBTPGMZit0TB2QVUpI71hIEyaa7J3L+VkkFGSzXDXOGDO7/LzwEU/m9R2DGS295097H1nDwCvrV3PmXf9G2mrx8esqprTEp4CEHgkV0aUZPyjU4N0DSZtB17bPjquobCzm/ZPxzGmy/9tDjNXTaOsOouhdieNn3TSvGv8c1VIDNuqUyyeqqJiT7PjGfbEnC5VVTm8u4W5P8lHdntpf2Ej2/6wEUVRSMlJJxLW5lRn+Gzwz/DhEQ5vGU/n5VeOEwuJ0tHBoeGIzIf7e2OUzaIoIIkCzwdfjaYW1CgDpIogSjT1OKmPEgKF4yIkY6kVFbA4TDgyLHRGHZGRXg9+TyiWY5++KJ+MkiwySjS++wOtw3zyibZjTpmTozkBgsIzdz7JjGUzmH3aXG749c0ceH8/s06dRf32fpLSrZjtRrwjo7Tu/pjDuzXgXygQYvNz65m1aj62jDK+uvZcjLZEJ2hBWTrvH+hBEASkkIwuEMEwIfqVnmRk0AUL7r6W3uZuhlp6EUSReWedREF1Nd6RAaYtXIggSIz2afOn00sxXMyY9TT30NOsRaxWXn0uETmZRedX85cHf8OMkyp5aPfD2JITsTvH7bj9K9hxJ+ALsqAvkgB0MtgN6CYIA9l1IqO9k1HnR6ihTFCbi4QjDHcnIt3/c/sDhANhfn7+vTEnQImM7/3bdnSQ1DyMKdlEzjnlBEIygiCQUVVE/ZaDZEzLJeD2Y0yysu/9/fid3oT+K0+cMeW9OkzpCFFyHWO2DbnQQfXMbEpzkmLRib7mPna+toOBlhEEVY7Vu8fQ+X4fO9Z/OGX/WaXZCMDiykzc/jAfHujFWJlGlitAX5OGUdj3bhMzTy0jJTeJlNwkpi0u4PCuLkL+MKqiYilwENJL9Dn9DLuDhHomE5+l15SiS7YhBsO07W8l4NUiFkVzShEFkY4X3qft4wb0JgNBX4CMwmxcg5pjNDrgRGQQa7KNldeeis6oI700hVBYYW+Ldi2PdSInSSKhUvzRic+ArBBjAxwzUQBxCuSxAFQUpOALRujf3UvLey3UezRg26rrFpAVVQEURAEhrjzR4jDGmPAAbMnm2Nje++3bOAU9UlUZltZRevb3IYoR2vft4PDuJt5+7HWKZ5Xw810PcerXTicSiFC79fUY/e5IV2PMAQAY7hkmFAjxyV8/Ii2/gfauk1AsFlJsRjJTzARCMg0tQ3jfaCQSlHENaM/i9DOnQ6GGaTB5Q4xG0fcRRaV4YQUpaalULV/GaL+foW4/6fl50d+hAkIaQXcHezfupbC6kJHekRhmZtr8KlLzMskqq8Q1FABURvr8lM6Zxo9e/zEW+2QugeN23P4V7LgT8AXZxPD934MHmMriF/cxE0SBtMIMfv7JQxqgyu1jqHOYd36rRYzksMJIjxtLOII+Ti542tVnkbewkqTqEvzRbl03PjDJCTjj5rP52i9vRI2WbI3tPt/57UFCAS00nJJj4/Lzq7XFPQ4TnCudAAAgAElEQVQdnZpv5YFL7jri/VQvrzniZwaLERWQJJGkaMg8aDdiSrEAmhMw0ushEoygi6NhLZ2XN2lBLc934PGH8BTYmbPpP5F0Ei4khlUJRRQJq2BQFfb/5jW2/+FdSudWklu+kLbf7Sa/ogbkbOaeYea1B39NwOvBO6SlL0a6Qux/71NK5pRy1drLY9eTFTUmrmObnsqcgnnIY+xyooCaKvPxb//KPad9jKSXECWR6mU1WFNt5EzLY9ZpGhxnKnKoI9lYS12rk8Y3EnlM3vnNTi69dzVSFNBWMCMTQRAIeEIIgkDudBtyWMFoN9Bnkhio60OnKDx206+wp9o489tXUH9Qey6C7nbMdhOVSypp3ddKJBSmq66D/KpCdCYdF31vOS/e+z5yWCGjZCbzzjaSVVKI3+Wht7mZvsMajiQpP4MmZ4TwiOY0ZySZsJt1dPvCZJ9bCUN+ap/XKgUa32ykYHY2BqOOxp2dZE1Px16cTLAkmTm3XIj3wza668ZxcIOdTkw2AwFPiNQsA+v/8B4A7QfbSctLI6s0m8H2AapXLGOk149zILH66or/uPL/lAMQDMtSW78nxR+W9Wa9FC7KtI0Y9dJxKeF/YTvuBHxBNimHP6UX8LeDbyLhKZyAaMhbEASsqXasqXbSCjJjTsAR+1JU9JUlhNTxJfPs39xO35a9/OXO37LiqlO56IeXEHD7KZhZnHiurLD4GiMf/kpDylecWJTAFjhmepMeW4oNz4gHQRTIKMygv1UrCa45ZzHFC6ZzaNsh5NDkMP3hXU1MX6zVaWtaBhpZUuqyQsyLctG5QkTcQbb/6SCLL65BFwfammrWrWYD1rxUsvK0HfHB1mF8vW7GeHfVHh9ZOZWc881cAh4vZrsRa8p42Dzg8VAypwRbim0SruBogHBPWCEjxRyn+aAiJZlo3pWIRwp4AjR+3EBqXhr3bvkFBrMR77CLv5z/gyhTouYs6M0GVn+8FqPFOKlCA6B1bxO+kUasqWWoqkhShgWrw8i2F/fjyLSSPCMdxaKnv1XbresMItZkE32tI5jtRlR/CCEgIAmw/OtfIq+kjO5942V2ZnsSO9ZvxpHpYObKWTTsqOcnq3/A451PgSCQVpjM1fedwY5XahnqciGoyfjcIoqaQun8pVQuXYogRLBkpyF4gjDm4IXlWHmpJ6KAwxiTowbo2NtLXnk6qgq9DYP0NgySMyODYERBDclYHKZxUSEgNdvOQMcob/1qnAwIYKhrCLPdzMpr1jDSO7n0OndaGrNOmXnkL/RfzPa2Dmc39bhyZGVcTnF/+0jBtJykntnFqcelhP9F7bgT8AWZZNKRWZ2BIIoIooA11YwQ3REKUR7xSCiC3hD38lFVFFlP58G++EMYLHqyoyHcMTPbTJQvLE84dnDTPhZdcFLCMRWV7FlZsfp1RAG9VT9JkAjAExhPV/gVOO3Skzl5RTWFc0pjvAc9Q17c/jAjniDzpmfQ0uvCbdFRc+Uc5EAEoSyVcERJKJ0SAIvdwn8deoxfXvUgh7YdYqR3lPJF5YiiyAnfvBDFZOLalfOofeYdtj72esK47r/oPzjtxjM5/aazsaXaObEyiy21fagI+MIKmHXoBTj0ah1BX5j551SQlGVDkVW8wz70Jj06oxRdQMVJ0YExsFlsHpqGGOl2R0duIxTyEwyMt1HkCC17WsiryMNkNSNHNIDemF7CrRU3kDM9h+TMZMwOK6lfOS3h+wBQQmGQZdTQZCZWz7AWolbCMjeVfBXQ0Pll88qo3Zzo0P37iXdw2U+vIBwIk1WWQ/GcUuSITMf+Vjb8+k/0tfSRP6OTky+9kK6mYVwDProahqg8bwbtXS7cXZ3sffvPKBEZQRKZc8Z52py4gwiKCpKA0D+EyWHFtKSMkowUmjc0odOHqftwN0npSZjtZpp3NRHwBJh31vxxwWRFZcNjO2NcBEG3i5Y9O6levgjXUDJJ6VbcwxHo6iPLGcYxKwvZZkTp9WpqhHFmyrGTWprCcBSMONHZinhDoMJAh5PiWdkEvSH620eRwwrdTUMUVKaz+rqLeW3tUwnn+d1+Wj49wK1PfheT1YDRosdo0f4rfQaJ17+S7W0dzq7vck4iC5IVVRw7/rc6Ah6PR1ixYkX5tm3b6sfq++Ho8rnx9qtf/Sp17dq12QBWq1V59NFH25YsWeIHuPjii4s3btzoSEtLi8SXyX3jG9/IffPNN5NFUSQtLS387LPPthYXF4fjpY0BTjjhBM9zzz3XPvGaR7Pf/va3Kf/xH/+RKwgCM2bM8L322msJfOxut1tcs2ZNaVtbm1GSJE477bTRRx99NEHo5cknn0y59tprSzdt2lS3bNkyH4AkSfOmT5/uB00f4b333msC+NnPfpbx61//Oqujo8PY3d29d4yd8PHHH0+555578srKygLvv/9+Ik/857DjTsAXZIZ8B6alRbG/jTqJg4/sSGjjyDCz8YkXE44Vzy5loD1xYcgrT+f87y1POJZRnEXDxxNDvW9NcgIUBQxLChKO6XViApo/dlwSkOOWx+T0JDJz4hQvBYGDbSP4QjJGvcjAqJ+6DiepNgMusw7MOrzDPka8QQoybFTma4j5sR6NViOfvjWOyG7YoY3/hIiCCPgFibIrz6Bg0Qy2/eo12qJ13gFvgBd+/Bwv3fM8GYUZzDtnAWX/dgahuJSIFEWR121to25rG6VzcxhoG8U9PO5k1dy0ELeikGk3UpOXHMUkaFwNJ0zTQHqqqqLMzWPzs3up3dyKziCSXZJKwBuaREpjT0ujbOEqUnNM+J1DvP3Yy7Hqgt4oyCwpw8HNN58fnT+txI2eAd64+1kattZOSoMs/NJydm/QtO4jkfGoSNAXpHnXZPZRn9PLf13xID6XD0EQOPmSZQx1DSZQRluTkxnscid4PlKeHTmiYMzPjBECFdaUYTDpuOC7y0jOtqMzSHyw/mMeu+znrPrOlwnJCpQkU3FaGd2fNDDYOUg4EMI95KZoZhGCKGDKTaN/xE96shlnr4u+lnEuB1taLgNtm3n/qb+w+Pzl2KfPwz2kKSX2NQ2DrDDa7yXoDWsh/9nZhM3aK0oWVLLXVJATURD9ESJ9XoTGQbJLUgkHZQaifAfZpSm07htfq+ypZowWPfpcO0lJBhzZqTh7h6Nts7n0J5ez7NJlHAur27+qBcOy1NTjOmpNalOPK2dGvqPfoJM+N2fAunXr0r/0pS+NxDsA8NnyuWM2bdq04NatW+szMjLkF198MemGG24o2rdv3yGAa6+9dvAb3/hG/zXXXFMSf85dd93V+/DDD3cD3HvvvZnf//73c8YW+6NJG3+W7d+/3/jAAw/kbN++/VBGRobc1dU15Rp6++23961Zs8YdCASEk046qfzFF1+MSSSPjIyIjzzySOasWbMS7tNoNCpTjWv58uWeCy+80Lly5cqK+OPXXXfdSE5OTuSBBx7I+lvuZcyOOwFfkE2kxFWnCBMfO05gCtGLKehIA1OI9HxRqiXBsMLOxqk5SHxBmWBInjTqYylzVFTQlRdx/uO34XX76d26n/2vbqV1xyEUWaGvpY831v2VVSYTGasWIlmjDHQTXk2uAW/MATDaDZRfPhtntARyuN/Lk/dtjbW9/N7VpOSNCSgJSJKIMRopScmyE/SHGRkDcAoKipxY4jjaHwTBRs0ps3APuVBVFVuKDe+oB0uShbnT0hPak2GjYav2W++o7aBgRgElJ1ThyCohFJAJBzZptxSH6hcliZzphegMEp21bQS8frJLs7E4LAx2DMbm98M/bqJ62bhjYU+zI4cDKKERQEO3Z8xIJxD9LsQ0B2tuuwpBMOMeDhIKRLCnWxFEeP3hv/DUHU8wY+lcHOZUQp82YHQkcXhLLyHPAI6MJNLy0pD0Omo3HyRvdikFl5zGjgZt51+abeeSH59K+4E+9rzbSGp1GVeu+j4f/vyPNH58gOScXCxJdnzuIPnl6bF0C2ghf/b1Ujg7m3AgQsugj6zVZYTNehS9iFSYxPQVpTRsTCzpnPjQuYf9+LwhHF+qRFQUvvzCD3j8lNspXzCdH79993HEP9DW70mJTwFMZbKiiq39npTyXMfnlhJ+8cUX055//vnDE48fq3zu6tWrY4vlihUrvF//+tdjZSRnnnmmp76+flJtbWpqauzH4/V6jyj5+3ntkUceybjuuuv6x1T88vLyJuUv7Xa7smbNGjdojs6sWbN88doHt99+e97tt9/eOxbd+Cw76aST/qE0wRPtuBPwBdlEJ2Aq9/mYav85AlHQFE7AVDzxn8f+kf6CNBVn+pGUhKYyUcAvSDiWzmHl8rlIvgCYDAiKwoHH/kzDGx/T82kTs//9SkSDfpKiUjyFbs7c3Jg6HWg8qLHLSAJ68+SfQfPOXeh0ApGggsWREUvHREJ+Xrrn6WgrrR8lSiXodwdo298KQF5FPl31ndhSbLHxjLgD+EMyu99q57w7b4hda89b75GUnoVzwE9qjo2V15yLHJFRVYVIMIISiZBRVIAz+votnnMSfYc/pXlnPb2HJ0doTTYTKdkpjPaNkj+jgLottTTvasRoNXLKTV/CurSAYEDTdVAVFaM9mf7D42yp7z/1Cdtf+iv9rX3MWrWIqqUL6D7kxt1fR09jB5VL57Dt5Y1EwhEGOwZJytAcqK69h2l5dgOFl64GIMVmJCndSprTyYGNbzNv1sUYC/Kw5qXTsucwb//qeRaddxI502sSHIAxS822MXh4BF80jeb6434EUWDaGdNR0s2osoqkF2PCTSlZRvrbhpiogzPtrAr8UQfQL+m5dfODLKnJxepITDn8XzV/WD4mKV1/6NjaxVsgEBA6OjqMFRUVk+VBObp87lS2bt269BUrVjiP1mbMbrnllryXXnopzW63y5s2bYoRkHR2dhpmzJhRZbPZ5HvuuafrWBT+xqypqckIcMIJJ1TKsswPf/jD7osuush1pPaDg4PSO++8k/ztb3+7D2Dr1q3mrq4uw6WXXuqc6ASEQiGxpqZmhiRJ6h133NF7xRVXHJXC+B9lx52AL8gm8rjLU6zkf0/FwETPVhAFwoEgkZAco65VVYjIMuYJNew6SWSqsvTlM3O1vLY6Nj6V1l5XwvXsZj0Wo4SKEKtL1+tEjEJUhEhVNcxDYDLAT2/U80JoPaoKIV+Qw7sa+eMPn5n6/uJckpCsgtGojUuQGOkY1ChgD7ajhiP0t/aRnJ1K+aK5JGfloSoq3rjqh9YtrcwsT8OjF5EVFV0caZBOLyHqREKBCJIkIEgioihQt2U/zbuaWHLRKrJKzAz3av0lpY9z38d/B3pDkN7m8bRfcqYD16ATn9PHW89swjhzGm5/BEGAjrrEBa9w9om4ong7o9VApCd5bBLQmQGVmAMA2rPlSM/E59qd0E/xrGJUVWXXGxqNbrySpKqqTJs/nYNv7OCtX7yIPS0J95D23Z5x44Vkl2opo5HuDt5+5JkYg171ijPobnaTlmPk0OY2+lv76D3cw7yz5jHQrqUDnAMuTvv2ReQtnYU+PRn0Ep4BJ6/98HUcqTY2Pf0ew93DJD3/HjXfu4Il37mUQ+/upmJROdtf3UJBVQfFJyxPmM9pZ0xHNy0F3UiAlnea8fZra4OqqDS+2YA91YJ7yEdqjh1LkpHB9sO89egbhPwhlly0EsmYhyAI1FwxB5dl/DWnkwSWzC3Bajz+6hszs146Jolgs+HY2sVbb2+vzm63H5HR7kjyuVO1fe211+zPPPNM+kcffXRoqs8n2rp167rWrVvX9b3vfS/7vvvuy1y7dm13YWFhuKWlZV92dra8efNmy8UXXzyttrb2QHzk4Ggmy7LQ3Nxs3LZtW31LS4t++fLllaeccsrB9PT0SUjtcDjMBRdcUHr99df3VVVVhWRZ5lvf+lbh008/3TJV301NTfuKi4vDtbW1htWrV1eccMIJ/urq6inCu/9YO/5L+ILMU9vP8JsNqEp0t2XRsfv1V1AUTYRGkRXS8tN5MfznhPMCHj9Xp18WI4BRVZWKJZVc8P1TEtr1NfcmSAOrioqz38mvb1if0M5kM/C1dWv+pnvwBSKTtA5A2+XLE5wcdXunFsKN2mC6BeOFNZQvLohFaEWdhKyoOH1BOgZ8yHm5nPnr25hfnoFOEnn943aNyKazj9b2HswLaxCiuVpBEBAjMuIErYNIMMxg+wCD7QPUnLI8tqM0WvUYDKMceP9jfG4f7bs3Y0m1MfPGLyEMjvcRCkR45WebGO2L2wwIIIia0NG2l9/l3G/fGPsoqhWCNdlKSnYSgqjg6mtAFFUyi7No29+KIisc3HyQ6uU1NGyvJ2wxE/SGEERxUrha0mvfoRpl8BEnRFAEQZgUMYqEZHTmHFZcfS6bn3sDg0lP8ewSGnbUE4mrrhhLE5jtZrJKsji46UCsr/gS04AvhC8KuEtKs8YcAICBlr2kFc+mv7WRjMJ0HBlJ6E0G9mzYQ+WJMzj4oUaIlb1wBuG0FMIqCP4QH9z2CN1jjsSyGjwjXmqWzkBf10TQ7KCguiiGcRjtG8FgDBEOGRH1IlWXz8ZllAiFFZIyrGReWIXVF+bwG434BryggnvIR+4JOXR/2sNQt4vaDzYT9Gnvy49e2sj0BZXM/fLpOM1SzJ006SUq8hzHHYAJVpRpG9nfPlJwtJSAJApKcabtc0sJW61WJRQKfSbCcqJ87sTPd+zYYb7pppuKXn/99cbs7OzPVbJ4zTXXDJ999tnT165d2202m1Wz2SwDnHzyyb7CwsLggQMHTGPgvM+ynJyc0OLFi71Go1GtrKwMlZaWBg4ePGhcvnz5pPMvu+yy4tLS0sCPfvSjfoDR0VGpsbHRNJbbHxwc1F900UXTXn755aZly5b5iouLwwBVVVWhxYsXuz/++GPLcSfgf7GFfBE8Q/HheSPu4URGvHAwPAnmLIhigrIZMGXIQJHlye2msr8r3DD14YmpDpgsY+sa9NHTOMj0xeOgRF8wwq6mQYbdic/12MI31kPQ4+fVO5/gkp/egntQE+VxZNtxDnhx9nsxmcYFl8YWteplJ+AaigPSecPIQS+9hzWAnmtAiyA2f1yPzqineFYZ+VXzSEpPilELj3cKShwor7u+DmtqCYqs4hoKMP/sxTTsqGXXmzvIn9FLy56m2HdhspkoqCrElmJj99saje3TX76XipOrWXjv9Rg6xp+BvPJ0uhomhsETJ12cwuECLQWh0+mYf/Yimj9tiFHjAkg6iZLZJTTt0gDDxbNKqNuaiDeS45yAjoNN5FUtBFUh5JcpnVuK3mhAEAXkSAiDoZ/aHQ0MdQ6SUZCBPqxSvmgmJtu4IqTOYiQEKOEIm7++NuYACIKAziDxeNdTGMxGtr10gD1vNzF94SI6DrTG8AuDbfUUL1lMztk1uOIZDgWNa8Fl0pFzcRX+7Z0Y7CbMMzPxhmWqTiyk9sk3GOpMnMfGnYdo3HmI7Gk5zPu3VSQvn4vJamB6bhLHLdGMekmelpPUM1V1wJhNy0nq+VtAgRkZGbIsy4LP5xMsFkvCg3w0+dx4a2xsNFx88cVlv/vd71pmzZp1TIvi/v37jTNnzgwCvPTSS8llZWX+sWtmZmZGdDodtbW1htbWVmNFRUUQ4Pzzzy++9dZb+1esWHFEh+CCCy4Yfe6551JvvfXWoZ6eHl1LS4tp7Px4u/XWW3NdLpf0/PPPt44dS0tLk0dGRvaO/b1w4cKK+++/v2PZsmW+gYEByWazKWazWe3p6dF98skntu9///v/LWWZx52AL8gmL+RTNJpiMZ1q3Z2YWjjSsSnH8Xny8McwFpjar5joBMDklIXbF57kAIy1S9jtRp2CgDOMO8rIF+9Qqao2tzNOmkFTdDfpyEonMqHUT1Umv7OCviBBX5CDm/YiAP7COVPc4fgiWVBVSl5lJcM9AfSGMCBjTXHgGtRC6Tq9kHDvAU+A1n2tWB2JBDN6vRFd4zCNUanj6AgnXTd+Jy9KMp7BJkyOsklzqYR7+HTDTlyDTqbNn8ZA27gMuz3NQXpRISl5GlmSoqpUL5tLyO/RSlYFAVEUURSNMVKQBPyjTdRu3oN31Ev5onLqt2sRV5PVxD0f/hwEgdbdzfxw+XdZcfX5+NxG7GlGZvr9eEbcvPLVByioKqBtv0aLXHVyFW0H2imqLmTvu3t57IZfUr18OfU7NOcg6BklEorEIgkAziEX+jI7dtkCooCUYUEe9iPZDch6CVWFtOXFiIKA06elmD1hmYpLV7Lzj+9PIreyJltJzkzm/bWvkPLCB3ztD9+e8rs+buPlfxN5AiRRUP5enoBly5Y5N2zYYDvvvPMSdkFHk8/9xS9+kQFw5513DvzgBz/IGR0d1d1yyy1FADqdTj1w4EAdwJo1a0q2b99uHxkZ0WVlZc367ne/2/2tb31r8I477sg/fPiwSRAENT8/P/TEE0+0AWzYsMF277335kmSpEqSpD700ENtY1LCdXV1loKCgqOmPC644ALXW2+9lVRWVlYtSZJ69913d4xFJsZEkpqbm/Xr1q3LKSkpCVRXV1cBXH/99f233XbbEdUc9+zZY7r55puLxt6F3/zmN3vnzZsXAK26Yd26ddlDQ0P62bNnV61YscL5wgsv/MPkhI87AV+QTVwUpwIBTgkMnALFOtViNtWiO9W5n9cJiCed0UsCJdk2LaWhwmD3IKM9IyCKCJI4HhuPyIS8LnSGMKhC1EkQmAiHFEUVu1lHKKwQnLDb66wbYPD5/YiiSEpVorTyRLOlWKheVk3x8tlUXbISVQXngTYivsHYHETJZJlxUhWIAmVnLEJV5FgqRpUVBFEg2ZyCGhqnZBaikAiTZQ5FM8sBFavDgtlupXnnTj7+y2YyCjXpYZPNhDXZRlZJVgJAr3zh9IQSPQCjyUJzggPAlI5hwOdHbwjReXAvzbsaiIQjLLlAT0HNTESdHlVW6G9r48OnN8TOaTvQRnZpNv1t/RgtRuacvghFTRS6ya80sP6+x2N/V51cTf1H42JNhTWFeEe9k8ZVeeIMPCN+5JBMd30PoqSLzbF7KIgjM4v972v6MGa7ORZx6Yg+NLVRqd6PXtpCyCdjSdW4Lfyuydiu1t3N9N/6S+acvhDJVBjDheTPyyV7RQlD3lCstNWklzQsil5k0AWXvvQj3vnmI3TWdaCqKjqDjrT8dIxmAwvXLGTuaXOZN3+iEuVxi7fZxam9M/Id/a39nhR/SNabDVK4ONM28rdEAOLtG9/4Rv99992XPdEJWLRokb+urm7KUr0777xzYOz/owvelIvexBr9MXv77bcn19MCV1999ejVV189CXA3PPz/2Tvv8Diqs+3/ZrZXadV710qW5F5xwdjgBjZgQrMhASfgEFIoKYS8hARSKCEhhBD4Agm9k9BsbGxjYzDg3qss2ZLVu7b3me+PkVZaaV0oea8kr+/r8gWaOXPmTNk5z3nKfXeLhYWF/pKSklMaAaIo8tRTTzUCjUP39Zf3FRcXh2RZ3jHs4CHYunVALW3OnDmek4kB3XXXXe133XVXe7x9XwXOGgH/IrTVVtO4XylD61/F5Y9UeANkSSazJJf00lG8fo9CY4oAWl2YT19/j5zynL4VG5TPn0Dh12bx8b6W6LfZc/A46+9/mSx7NvQRD0365nzSZo5FVKmiZC0aT5D6FdW89D9rEEURUSUw9Yoqcitjy0oHi8rIg7ap1SpGFgyoz/3zlbW8+ouXYo4dnHzWj/Kp5Rz+9DAW26XMYFy0v5QEI7NGGwkEw2zY2xJtv3ZnIxYZcmcWRg2jhb+8jrDDj0odUfIjhL6JXYYTh+uo23OMibdfSThFydp3NXWw8a8K54Kyyh34bunMBsbfNxDXHwz3ymq6G2OTe81JBiIRK+kl2UgRGSki0HS0K1p9YbYpE78syVHeg+yybBLTbbi7XTg6HNinVFJ2zsSoMJTebEClGUgqDLh7qduzA1nq4/DvY3tc88QnFI0p4sjmQ9G2WpOZ1uPO6IP59NUVMeMNeAJR48bn8nFs5yHyx6TGeA9ORztstg0YXoMNx4bDzTz741X0OV+Y/93rkcIRvE4foigRDPhIzkqmq7mLcChCxYxKGg81YLAa6WnpjjnH9pWfMWtZEZGwWsmPAOyT7Wh0GiJhCa1BS/2+Ojb/8yNmfn0pkYiK8ssq8KYacfclvPbDH4rgD0XQqZWQRECrI60sB1tGIu317aTkprL/w33c9Nh3qDr35JTUZxELrVolfZEywFNh2rRpvm3btjnD4TBDuQL+XZCUlCStWrVqWBnjvzOefPJJ2/333581cuTIM8pnOBlO+0QEQdADHwG6vvZvyLL8C0EQfgcsAoJALbBMluVhFpYgCHWAC4gAYVmWJ3yZAf+noKe1K0YwZSgSM1LpbfMCA8/Plqbi+O7Y97B41hg8Eng8AxU2IW8g6nbtRyQi4ZMAacAlnqAScXTEukhlKf50cCb+gvg5CMO9D6YEkyLZuquWu8+9g5GzpyBqbEy5rIKUfBsavZpgOLYvi1WPL3OgZtusKeb1K+6JiV33I7ciD4C2PbUkn68YAckTB8SNBFGIX5MZB6ZkY4wRoDNqCHpDhAIRBEGk40Q3iWkmTAk6Pnp+ezQZc//G/TH9NB1por2+A0uSme7mbkommnBEExBl0k3amPi/WtXLZ//YGHsPki0EPAGajjRRMKqAur116Ix6VNq0aKij8Lwi8pb/AW2vQyE2CoTY+vDr1O2ti/ZzYn8dRWOrkIiVD47BEC+UtTSHb9x/kxIqAM5DxrvtOJ5GLxqdDmenB78nhKe3i8YDB5Elid62blpqFIK3Wd+7mIKlc3n367/B2enE2emkZEIpDQdPkF6Yjt/tx5aVhCgGQXTjdzkZNXsUe9crXgRRJZKQmoCjXfEQOFqPkmofA1kWCEXwBsIkGDXDSK6OvvkRR9fuxJRsZdc7mzElmPjeUz/g3T+9wy9W3cjKDKgAACAASURBVHPWAPg3wa233vqVGhZnoZAF3XjjjZ87WXMozsQsCwCzZVl2C4KgATYJgrAKWAvcKctyWBCEB4A7gTtO0scsWZZPGg/5b0QocOpqGjEOJWm86bl/xRTTLo6LX45HhhEvTh+vfv8MIYXjzazDz+FxeDg8qIony24nHJF488GPUWtEisdnoy1NIjiIulgY2k9EIiEtgYyiDPwePwZLv6LdAK2uxjDAESKmJkU9APkj86PMfaXnjWbGfTeCIGDUqfH4Q4T66vpVoQiCVoU1xYizUzHGkrJMtNT0ojdp6G1XKgbMNgO9bS6sKYn43F5MtuEEM6l5qSRnJ3P4M+W6h4Zwhj6eeJGgxPREXF0unJ3OqKEjRSSsyVq6WxQvhCnbghMIJiriTOpAgEOfKF4Dk81MflWft0kOYkzQKTTJAoSDcNEPvs7KPz1PZkkWKo0K+yR7XwUKtG4/Qt4FE9DmZRABQoAuyUbj9i7Ah84QJuhpovlQMzXbFabHjJJMSibYCYkiBUvnEpJkFj38HZ6++G4AfG4fpZPsdJ7ooL2+nfb6dqSwRO2OmqinJjk7ma6mLkonDuQhAPQ6nIxeMhJvKIIYluBAB157MmkZZrpdAcJ973bb3uNR4iVQ3r3eth7u/+iB4Tf4LM7iLIbhtEaArPjg+uunNH3/ZFmW1wxqthm4/Ksf3n8uwnGEcAYjLk3pGZICxcsRiN/uS1QGxEE8T8CZFB8MHkc4JHFkcwNZXV4MuQmoTFpCyYaou1mWZfS9flxb2+lu7qa7uXtYf/mjCgDY+PA/uXjaaGRZWUmKahEpKJGcnU5iRgaRsMTIpecqPAPIBMNB1ALoHH4ET4imj+vx9PpJL7Th6nKjVvvYs3ob2RVVJGZYaa119l13GLU2zJgFi9BowwQ8bcABRLVIyfgSelp76ahvp+OEEsYsnWRn9NxpBHyK4SIIoNGrSUwfIKcJ+XxklgxmahVITLfRcLABgI76dirPrUSKyHQ3NYCosA66zdqYm966YSdGqxGv04unxx2tEqhARUpBLBNsUqaRzJJcKs4dw8YXVhMeoltQtr2aHJUJwaRBVgsEnAOVWkc++YQTB+oAKJlQSuuxFsyJJlIL8tCZc1B1eAglGwkl2Zhw+XR669qo2X6UpsONGK1GKmZU0l7Xht/tiwnVqLQarn3hpxjzMyj5aA8r/+dpABxNXfia2jEarbSvO4azw0NKiwvjhaWEJRmbSUuPJ8jkZXPZ/s9P0Og0FI0tYuzcccz51lzO4izO4sxwRgEaQRBUwA6gBHhMluUtQ5p8E3j1JIfLwBpBEGTg/8my/NcvOtj/JIxaMpvsi6YqcVlBEe4RBEAQEASBlAQj9qJY1sijWw5xdEufW7svsD92RgUjx2TF2AdHTSLfXPHr6N+yLGNJsSL2Se72Iz9Hz+jfzFEywPvOrTdp4mb9n84/EIlImC6eydcvnonQ117lD1P30j4yy6fQf5mCICDgiPEESJI07ASyJFO7fiD00WzSYEzUI0dkGlrdmBP1Jx2L2OcdcbR0o/P68BuUyValVhEOholERMwpJQDoNFYCyiDQNbkRZJnqVbGqfb3NjRzYsJHOBmUSr9lRTZY9ByksISPT22TD0e6gaOIMPvjby4iiyJi5Y3F3uxTq01QrHfXtCILAD1+7g4mXnkPt1kZWP7E1eg69SUdv20BoxpZhpaWmJWYcSdkDIlHt9e24e9wUjCqkq6GBjBKDIjoVChM4Uo8sScjhCFuffh+/x48tw0ZP64BnMBIKo1JFEMUgoZCh7xmKTL5sAd0tAc6/4Xo0mgAf/P0fWJJNaHQa6j7di+xR4/dqUelVTF4+iQXXjSfg8fHN9Gf7xm3DnGiiaGwxNTtqqJo1i+7WIKFWN772Nvb87T0kf5Bjg8JaXqeX7qYu8irz6Woa8Apf8fOrufSOy6nv8nGwoRfbzHEs/2QMta+uZ+uL63nl8nuZfOm56CwKLXxnfS8jVCIOKYJKJTDZnoJaTOMb913H/G8viLIznsUXh88dUB3YVG/z9Po1pkR9qHJ6fo/BrDsrJfxfjDMyAmRZjgBjBEFIBN4UBKFKluX9AIIg/A8QBl48yeHTZFluFgQhDVgrCMJhWZY/GtpIEITlwHKAvLy8L3Ap/14w5mfQ3n5yNkp9ugVreuxHy5aVGHXt9uPqXxkx6GPZOlUWE8HEIe5mo5bOIeV3owqTsBhjabWHKuidbnt0vyBEF6BR8h8YVpYHoNZaSC/MwOv0KJn4UhhBE0Hod/rL0NvuIjHdFJ0YA54QAU8IowXUmhChQJjzvn4hPS0t+D0+jnx2GK1Bx8SLJ+F1emG3cq6Nv3qecx/4NoGIzIJblhLxy7h6hWjo4uj7R8kpT6HxsBKNUmlEzEkG3H26AoIose3ddfS29ZKSk8LNf/sBWr2We+b+PBrSaaluYsT0Cna8+45y/bJMd1OXwloIGK1GqmZW8bX/uYrK2aMByCg+RTxe6WT4piEeHlOiiYOblJX9gY/2IqpEllw7lX98++Fhx2YUZzLyazPQmg3sf+dT9GYdhzetp6O+ncIxJRSNr8TZMXBanyuID4GKGRXs27CHoE/JOUktzqPy6rmoUoyorDo0ejUavYUfvf5T1jyxis6GTnavU27+9KsuoLtVOa5x60E+eXUlfref+d+5msTsLHau3BQdX3J2cjSJsnzqCKq3VHPxjy5Do9dSnKXhYIOSTuSVRWxjSnD9SSHR2r1mC5Mvz4e+qjV1MIJBp6LTGSAUlpg9MovLf3rFqe/1WZwRNr6yN2PXuprMcDASjUFuemN/7tgLSlpmXj3qrJTwfyk+l1ZmX+Lfh8B8AEEQrgMWAtfIJyHCl2W5ue+/7cCbwKSTtPurLMsTZFmekJqa+nmG9W+J062s44Xw47n5z9QFL38J5v945D/94/n0lY38bvGv+dsPnhi2X6uPb0NmTchm8TN3MPU7i/D0evjopdVsfPYFPnz2eTY++zwbn3ue9//yFI37h1fRfPLqm6z/+0us/euLfPj8e5xzxTR+9fEDvOh+gxecr3HLiz+mcLSyMiybUkbE7WXjbY9iEGR0lmQcnfLw3IVBlxcJSciSzORLR1B1XiETFhTR26ZMQA/vf4yq88dgy06OyekQVCJtx1uxpihEM2XnlEcNAFBWurlVBVTMGhXdpjMNoVkf+nji3HOD2UDF9AoqplcwYtoIcivyEAfzO8vQteqzYccBVFw1i4Klc8hYNJ15f7sDpzdAS00z4VCYo9sO8+kba1Crh5fl1e+rixoAAI6OLpyqEM6IFJONP2nxVL7/7O00VyvUyFlVBZQsnIR9URkFM7PY8PSb2DKTmP+dK/G6dWSWVmKwKOEPS7qNqmsu4Jvv/YYbNj2CSqtmzNwxrH5sBdvf2czhzYeJ9A5Uj+ntBUz4mqKGGfAGCPua0SfqGXnTRHosWsYUJpGRaCA3xTSMYfEsvhg2vrI3Y9t7R7IHGwAA4WBE3PbekeyNr+w9I7GbeHC73cLEiRPLBqti9qOzs1M1f/78osLCwsqioqLKdevWDRN0ePzxx5PsdnuF3W6vGDt2bPlnn31mGLw/HA4zYsSIilmzZpX0b7v99tuz0tLSRpWXl1eUl5dXvPrqqwkAra2tqsmTJ9uNRuPYb3zjG597tVldXa0955xz7Ha7vWLSpElltbW1cfUUnnzySZvdbq8oKSmpvOmmm3L6t//pT39Kttlso/vH9Yc//CGqLnb06FHttGnTSouKiiqLi4sr+4WRrrzyyvyysrIKu91eMX/+/CKHwyH2nyMvL69q8HV/EZxJdUAqEJJluVcQBANwAfCAIAjzURIBZ8qyHLdEQRAEEyDKsuzq+/+5wL1fZsD/MTjNtymeqlW/EE3MtjjJeHHtrbjEQ6ceRCAU4US7i2OtLibZ07BZdDH7Gw6c4JGv/4H8UQVc8YultOkNqFWKSqDbF8YTkSi/rILD/1QSswpmFmCqSscVjuAGUuefww0zx/DqtffhahuexGpOShq2behtOe+68wEBjX7AoyFLEkVjiziyWSmzzavMY9UNv8PT60alVjNy9lRkwarUi2u0+L0h1DpVNFxhtGqZeElF9L4t+/0NjLxgNDpzX1jBZOBb7/0W0aBF1unY+uvn8Dq91Gw6wOjF0/B2xU6mRquRc6+dFfNMHe0ekrOtUfpnY6qREQtK+6+A9g93kWXP6gvVCAiiQMOhBtrrBsqBSyaUxBiBkiSx5vf/oHB0IT2tPVHjJTEnBduEcvx9TSOSzJwHlvPXGbdGj80syeKDv79FYrpN4ULoCzdZbBb0Zj1+txL/l/wBNt/1NwRRpCY/jeDVMxh/yTn4A2E0iWZuXvUb6nccJXX2BMKSTBgwZBiQJImWo82UneMDDLi6/Ey/egEBIYB92Tz8ERmVVoW7pYsDH+4DwNXp5Oi2oyTnpODpcbPoz98n2OOia/8xAg4PGp2W+TcvxZyUiNMdIqASSNKoaeryMqMiPe5v6Cw+P3zugGrXuppTSgnvWleTOXlRebvepP3KpIQBli9fnjt37lzn6tWrj/n9fsHtdg9bmJ5KShjg17/+dXpJSYnP7XbHJFrddNNNbffee2/b4G1Go1G+9957m/fs2WPYv39/jDFxJrjllltyli5d2vX973+/65133rH88Ic/zHnrrbdiuApaW1tVd999d86OHTsOZWVlhS+77LKCt99+23LJJZe4ABYtWtTz3HPPnRja9zXXXFN45513tixevNjpcDjE/rDnE0880dCvbXDDDTfkPPDAA2m//e1vW/83pYQzgWf78gJE4DVZllcIglCDUja4tu/HuFmW5ZsEQcgCnpJl+UIgHSV80H+ul2RZXv1lBvyfguxkEylWPcFAmCOb6gfq7wWBcCTE/o92UG9QK3X+MiRlJ1M+rYKfrfhFtI+Wo01sfXszx3cfQ6VRoTVomfvtCylIN5OWaFAyu4FIREarFqMZ06DE3LUa8aTx/4YON3uOd0cpaXfWdHLuyEw0g6oW8kYW8JeaJ0nOSwVBoAB4f3tDDNGPP81E+aUjEHOsuEMRXENK+vwGAxf/fjkvXqtka6u1ahIzbJhtJpJzTSz59WUx7X8y4aMoG1+mPRtRPTyB8vju49Hsf4ATB05QMb2CxkNKUl3N1p00HGpAEARmf3MJnQ2xY/I4BoVNBIEFt1wcsz+s1hCw9tXNSzD9F9fjrD7BrF8tw6fRUf/S+/hcfiXVQxQxJZowJ5lprWlWSjAlma4mBz2tvUh9CytjmolwaGAcIUmmubo55rx600AeRMGoAmq2x5aYWpItuLpcHN9zHFuGjfyR+bi6XCTlpeL1+BENA8dHwhFsmUnRWn1BFMkuL6DpcF1MnwaLgYAnQMWMClRqdZT4B8Dv9PKHNz9j0rWzKFl+ad8grKSdPwFREKLvm6uxg9JJdkKBELIUjBrAkqRGkp0Kp4BJj0Grort1eKVYRlEGx3cfo3H9Dlw9Xvav2IyoErnkx8vpavIRkfxklyaT5AszcqQNi0Fz1gD4CnFgU71tqAdgKMLBiHhgU51t/Dz7VyYl3N3dLW7ZssXyxhtv1IEiu6vX64fFF08lJVxbW6t5//33E+68886Whx9++LSTodVqlebNm+c+cuSI7nRt4+Ho0aOGCy+8sAFg4cKFrqVLlw5bhR85ckRXWFgYyMrKCgOcf/75ztdff93WbwTEw44dO/SRSITFixc7gajEMgzIIkuShM/n+8pkkftxJtUBe4GxcbbHdUH0uf8v7Pv/Y8DoLznGM0Y4IrH7eGw2eX/Z3bbfv0Y4GB5YRcsDK2pZlqMr6RlLzmPUvGGXG4ODHx6nYmbhsNV+f1xdAJKtemTA0+vj3SGJaInpBt556JmYbZUzq5hy+XTGZA6sjrNKs3j6tqeif1uSrcz59oVYTTqsg5xm8eL5gwmA4rXLTDKys3bg9+wJhNlf101emplkqz56acn5scxzM6oyWLd7YPKSZBlvuokknQpCcfIDAmFkp4qCUUV0Nrbj7nZHBX9MieZhS3+DecA4V2vUdNZ34mj3I0sykqQIKuVW2tm9JlZBr2ZHDaWT7BzdWo05yRwtF4wEnTCkXl4+zVomPCgso1ULaPQaKMyhn7h49I2LyF86L9pGEOAPyx7h+KexhF9TrzgftV7xBA792aqH5HkMXo0DJGbYqEw0A4qRqDfr2blqIHzS09oTTQR0dDgoXrMFU1E2SEq4o6u9G61eQ/nUEaTk5ZOUnYUsaUnO3sneD7Zin1xG0KdwEsiyzMGPD7Lo1kvIH1XAikeU3AdTghFJkhDUsWMNRWTSEnS4/SElzyMlgaNblbLBgDdA6ZQ0RMHBRy+sIhQIkfjeNubcdwNSZSGiJ0RiZgq9LZ04+tgFD3y0nxse/TY586dwrN2NNSORE9uq2b16AwVjp+Lu9nFkSyPnl6ViNQ6Tjj+LLwlPr/+MJILdPWfWbjBOJSV8+PBhXVJSUviKK64oOHjwoHHUqFGeJ598ssFqtZ70FzpUSvi73/1u7oMPPtjocDiGrRb+9re/pb3yyivJo0eP9v7lL39pSE1N/dIJjiNGjPC+9NJLtp///Oftzz//fKLH4xFbW1tVg0WNKioqArW1tfojR45oi4qKgu+8844tFApFPwGrVq1KtNvt5qKiIv+f//znhpKSktDBgwf1Vqs1Mnfu3OKGhgbdueee63zsscca+70nl19+ecGGDRsSSkpKfE888cQwtsIvg39P+qYvCFmGunZXXNn69c+tJ+g9vfZEwdgiquae3AiQJZn1z+9CY9BQPDEn5usebzI+0zK9uLX/Q1z8BsvJM+Y/L1QqkSllqWw+EmXnpKHTQ0Onh3njstFp478aRp2a3FQTDR0eiEiowxJhnRq3P4xWJfSV44HWEyR4oIPje1qRJZnCsVXU7X0npq9+Br7BSLZnU6pSKUQ4ksz/u+0pMvKq8A1avQuigfnfXcKOd9fF9uf2MWJaBYc/PYQsy2gNWlRaI9KQz08o4OE621XIkkx2eQ73bfmD0i/K86tpVr4x6kAEXbsfjySj06kImxUO+2EKijJccP+NPHnubSe/4UOFonTKZGawGEjJSSEpO5k9fQl3Gr2WnuZu6vcPEELllOegNWgJ+oJodFo0Oi1epxtRpSK9IJP3f/dGTP/2yXbajrfh6fWQVjyJ3vYI4CNv1DjSi3P5+KWVMUYHwDX3X4+oVlG35zgdJ9pxdjoZMXUER9duJynJxJV3X41aq0aWof54O23+MIIgYFCpSCvMIuDx4+n14nPUsu2dTwfdIJFItZPDn+0g6A8zcdGlCCIYLAI7V36Az+nkghvn4w1J1LS5Kbr+Ioqvv4gN332Y3uaDVM6aRkq2jaoZBSe/v2fxhWFK1J+RRLDZdmbtBuNUUsLhcFg4dOiQ8ZFHHjkxe/Zsz7Jly3J//vOfZzzyyCPN8doPlRJ++eWXE1JSUsIzZszwrlixIoZr/Lbbbmt/8MEHmwVB4NZbb82++eabc19//fW6zzv+oXj00Ucbly9fnjdixIiUKVOmuNLS0kIaTaxtlJqaGnn44Yfrr7jiiiJRFJk4caK7rq5OB3DllVf23njjjd0Gg0F+8MEHU6+99trCzZs3V4fDYWH79u3mLVu2HCwtLQ0uXLiw+NFHH0257bbbOgHeeOONunA4zPXXX5/397//3XbLLbd8ZeRL/1VGgEYtMrU8neNtLlp6fDEJb/FId+LhdFz7fm8QKSKz6vEtLM20YMuxntI1eabc/fG1BWL/NliNw9p8GaTZjKQnGGhzKJOxxaBmfElqjAEwzLARBCqzEmjf3kzLlkYQIXVRGcIxN+17WlBpVVhTTdTsii1/MydnYZ8ykurN+6Lbju2s5fDHByifURndNuVHV9HaE2scWNwhDrwYFd9ClmS8TjWmRAt1ewe8jIIYK+ZTOnEEoaDiWVDrVKRkJyAI4O5qjRogg/kcIhEJR5sbgzeMr9dP4Fg3h3a2RHM1BAHyp+TirkqLihz1Q5IEJi+ehUqjRm8yojHosCanoNEZkGWJXe+8R/C1AH6XD7/bR05ZDqJKxOfy0XCoAXfPQCWJNcmC3xM7QTcebkQURebdfCM+VzA6HlmGrFIbr9z9R+JBVKtQa4KEQ4rR4ez0YUtPHWYAjFswjXVPrkEQZRoPNUTzDaypCXQ2dLLyoX9w2Q8vxWBMwO/2cc/471M0oZzR8+fgdgcpnz6Hghn5uFrddB3t4pIfjyIcdIEsotFb6WxUri8h1RRlsfR7YcrXFpFRnAiiiNmg4vzRWXxyqBV/UOKCx2+n871PUKlUVE4vQHNWAvhfgsrp+T2b3tife6qQgFqrkiqnF3ylUsIFBQXB9PT04OzZsz0AV111Vc/9998fNwExnpTwpk2bzGvXrk3Mzs5OCAQCosfjES+55JLCt99++3hubm70h/29732vY+HChaXx+v28KCgoCK1Zs6YWwOFwiO+9954tOTl5mIdh6dKljqVLlzoAHnrooZR+XpjBHoPbb7+941e/+lU2QF5eXnDEiBG+ioqKIMDFF1/cs3nz5pjyMbVazZIlS7ofeuihjLNGwCmQlWQkK8lIc7eXTYcGckJisqxPgdOt3H29Ax/Pl+5ehy3LwpwbxtB86ESfV0BAFJVEL0EUCfqClJ2TjcFs6Nsm4O7uoWB0Qd/sqqhGZRQNf/dD/ljPxcgrZ7LlcBug1P33nY70BAN56acW3YmH9mPdtKw4grPDQ0K6mZm3TRuWbd0f3gAIBsIc2HCMnauq8ToHxmb9pIHjRwfeye5GJ1mlyTQP2ubo8FE04RwsyVZ2rvo0ep9/u/Ae7t/6B7LKFLe51aAZZgRIFg1aXQApoiIcFlFSU0QyinNjjACzzYzX4Y1SDfe29VA+w0rQp/DOtx5TQkUG48Bvtt9+Cwcj1O9pYdVftqCz6kitSCVhUg6GegeedmXSkmWo+6yB0VNyogyNoiAgBMPs/ut2dOY8su3JNFV3kZHdX5boQWtQc3DjQKwdIL0gLSbpLxyKcMmPb4o+05DPwcpHX445ZtyF0wgMos01WHRc9tOZWFONMUaA0WrE61BydZ0dDgRBRtSISCHlfD0tAzlJl/z4BsJBFT53iM3/+Iz2uuaoAaDRa5H67qVao8acrLxjRz6pJugL0nz4BGkFSumlJdOMUJVGxqh0rDkJGDNMBFtcHP2wjpwyPXqzDmORDWOmhdpV1XjaPKi1KtrqemmrU3InJl9eicmkZVKmiRUvbUKbZMU2pYrzZpSRbP3qvGBnEQuDWRcZe0FJy7b3jpxUSnjsBSUtXyQp8FRSwnl5eeGMjIzgnj17dKNHjw6sWbPGWlZW5h/ax8mkhB977LGmxx57rAlgxYoVlt///vfpb7/99nGA+vp6TX5+fgjglVdeSSwrKxvudhyCM5ESbmlpUaelpYVVKhV33XVX5pIlS+Iy4TY1Namzs7PDHR0dqqeeeirttddeqx06rpdeeimxqKjIDzBz5kyPw+FQNTc3q7OyssIbNmywjh8/3iNJEgcPHtRVVVUFJEni7bffTiwtLR12j74M/uuMgH5kJRkpSDNT11+rf4aegJNUOkbhdcTe/55mF6/d+zF73n8ryns+FC+634jJbv/k5Y3U7amLaVM+bQRDYUpN5IpHvoMgioiiQOqoYryBMB5fOIYa3+UNIQoCuWlnTpbSVtvF+md20tXHm+/s8LDj3UMDWfMMzytY9efNnNjfNqyv5qNdZBQlRSdZgI4GB6ZEPZ4+o0mtCdN9opaW6uNkVuRTcdEkkoqzMWcl0+CHSIeb3FQzualmapqdMdfnkUFMlln3wIuoNWou+N5lFF41k+aPNKhWfEIkpBj9ri4XRWOKOL7nOLIs03TkBGsef47zrluMs3PguWqNSSSkJeFo7456iNpqulj1F4UDy5CoJ3F8Fs6wRPLiEVw3MTfW2yPA+zsao/oHFo04zGvT3ewipyyFxiOdcas+hsLr8NDTOvCdMljMTLx4JttXfBwtHU3JzcbrHujr3CWjScy0gCxzyQ8Xs+mVjwh4Q6QWpNF+vA2dUY8sQPE3JuNVKT91Q7uH6rdg5jeuA0FAozPS06q8t2nFI9j/4W5MiSasyVa6W7tpqm5i/EVTSMio4PHlb0WvU63V4OzoZc/777Lk7z9Bk2IhohFx+sJgT8InCtS9oXAcNB5RvpNVc4rpjUikLB5BgTNAzVuH6F+u7fvwGCcOtDHp0iJev+d59n6geH4W3XoJCfNHnvb+ncWXQz8PwFCeALVWJX1ZnoCTSQkDPProoyeuueaaomAwKOTl5QVefvnlOjhzKeGT4ZZbbsk5ePCgASAnJyf49NNPR2Nr2dnZI91utyoUCgnvv/9+4nvvvVc9fvx4/5lICa9evdryy1/+MlsQBCZPnux65plnohZ1v5QwwE033ZR78OBBI8Add9zR3G+8PPjgg2nvv/9+okqlkhMTE8PPPPNMXd81cf/99zeed955doCRI0d6b7vttk5ZlvnGN75R6Ha7RVmWhREjRnifeeaZr0xGGP6LjQCAMYVJyDI0dnkYuWgyW59bd9pjTucJGLwCHozSSZVUbzmA1+E5LWVwXMQJKSRn2LjiuwuGbd92pJ2WQatltz+M0xcalo/Q6w7gC4SRZBmLQUtzlwejXkVPh4/DKw4rBoBIVGxny9uHyC5LJbs8dVhSIcC8b0/iyR+8G7NDo1dRevEIpDQTzlf34+1z9Yb8YRJTTQS9Djrra9i3fieSJGHNTOKyZ38WzSwPAm2+CG21XciyzIl1x+jZ20LqyAzERD3BBB2SWkTTlzAYDoVRp5pxhSUsU0cz52dXs/qeF6LjObb7GAaLIerul2UZUaXGmqLB2enFYNFhsOpZ8N1rObDxMzw9irfC2ekhMd1Mb5ub1Mo0nIMqIOJ5kU5mUireHtAZNEQiElUzC2mtBfzHIwAAIABJREFUbSWjOANZBq1ej0anIyE9lWx7DuFQKDqxqlQCkT6DwecKYkgoYNT5Afas3Ywp0YJGr4sSeKfmJ1A4PqvvpALXPLCMS3+2hOd/vIpwSKJsqhaNTgU6X9QAAKKy0H03J7aMVAqRV5lHOBTBkmQmGAihUmsBFSarDkElEPR5CQcCUcPE0d5NxKLFHZZQSzJpVj0S4NjTGvU89CN4qANdqgkvQT594i18nS5yKkYQDiv8C44OD5++vo/9Hw6IM9XuqEF9hl68s/hymHn1qNbJi8rbD2yqs7l7/BqzTR+qnF7Q80U8AINxMilhgKlTp/riTehnKiXcj4ULF7oWLlwY7X9o2d5gNDU17Ru67UylhJctW9azbNmyuGGRfgMATi5x3Oe5aIq3b/Hixc7FixcPkxPeuXPn4Xjtvyr8VxsBWrWKyfZUxoaTaH/1zOLpn9cT0A+jrZQx80tRa8KE/d2kjy+gvb6Fjx97B2SZiCSjOgWxyecp+jjTEhF/MMK2o7HeqhSrjk5XAMPMAuznF6FxBjj40l7kiMyMq0aRaU85KXOgzqxlzvKJrP1/2xA1IvZF5UQyzbjDEoQiFF9ewb7HtwGgUoc5sW872979NKYPZ0s3cksHpKcM63/7llp6tx7A69DjaFPkwK0pRlIW2gm5B4yezHNHRzP1c+dNQvz1S0gRiQvuuIr0qgKliiAi4WvrRXbo6GlyY0lSRz0TPlcArUlD0eTJeFyKIe/s8NDbpsyw2mQjglbErNcAAl3tLnrqegl4gsoEKoPK4UMfUaoVLJkW5n17Il1NTo7vaiEp00pXk5PLvzWTjNIUPnl5I7IMbcdaWXT79Tg6lDucVRZm27sDrHqVhIHYJGdLciIlEysomzoDR6fy7tkn5zLn2xOHvQc6gwaDRYer24fXqeQN2LKHholin64U8aPWBAEBv6srhgQpf2QJ+WMU0p7eDg9aXYBt76zG5/KSUZwRlS72NrQhJCViTbLS3qc30HFgP1v+8RbJOalklxWgNeo5sU3E75ZA6mTPSuU9cXY6KJ86DgQRtUaHWpPGxT+8gR0r12JO1HP78z8c9p6cxb8OepNW+iJlgKfCWSnhfw3+16SE/xugVatY/tC38HY62fjih6dsG4+1bzBOZgT0IxxSk3/eRKhMJXdyBd+6fCabjnQiiAL5aWby0sxx2QI/T+2n6isqE/WHJfxGDZO+OY5Ro7PQmQZCFkMTAoPhCMdaXBxTQdX1Y1HZ9PR4QzBoxeyQZPKmZXDw7Y3sXrOVwjFFw86pNelRZ6VGV7ygGF4967ez/sHX8Hv8zP7WEsLB/kQ2L5G3DhMOxH/PQ4hMvWEBZYunE7ZaFGOrb5/Fno9JI9L75624umNDgpnjs5FHpJCqqeDwiR7kQhuWLDOpVenobQZcwTD+oOL1cbS7qX12d8zxBos2mqCXuXQ0pTMKKQpF2L7iyLAxTlsykyOfHmL14+/1rbz7SlOHKg2KEkQGjACjVcu2d3ZxztcWY7IZCPl6cXd30Li/g2du3R0lP+p/oWZ+/fxh1ymFh5h0soxaE6SnqRZBFNn6z93RJMQRfSGp876xCK1eh85kxGxLpuloF3K4lQ9eUDTD9CY9TUcGFjMvXvsA4+ZPpvicqZim5xPWqUi5aBqWF9fTXN1Ic3Ujo+dMQKPPBEQS0nPJqSjE2+sgMT2Rfes309XUxajZ40jMVmpfS6ecR9W5BaQNKVE9i/9MnJUS/urxvykl/F8BlVrFrc/eRlZpFm899CY+d/w8kdOp4nkdpy4zzJ6QhViVFi0j84lq8CvhgX11PSevFvgcE/uZVjrERZzT+yw6dEPqr/sTAkMRibo2F0ebnIT6EtmcOhXGiIQoDKgVS5091Lz8Advf+Dia8DaY/KYfk6+bQ2CQARDpcbD7969x6MOBxLnUHDUdTSrCASUpzdPrR1SnsODmJbz/xKvkp5qQdVq6nH5kGUYtm49XEoaV7gF4QvGNOkFQrtEfilDdrORF5F5WiXdfOx1DwjmReO7oQafqV3BUqUUmXzqCLW8ND1f2P7PBxp48hLBAFEOoVB4iQcW48HRLjJl3Ec4uH84uHwfWr4sR4BmKPWt3UTRhduzYg2G6XjuALElIEYXISKUxkZw/EpNVYOeqzcP60Vsy8ToD+H2A4Cfsb2TzPzZE96viEDhZU1Npq+mmJD8RuSAB19YDZNmzsSSbEQQRKRQiElHuu6PDh33KFHasXMXhT5V7pdFrGbdsAZ37HUQCYTLzbYyY8p+vIXIWZ/Hvjv8zRgAo8r1LfrGUBd+5kO9V3hxlphuMU3kCBGD2N8ejM2nYs6522P70qnR0E7NjmPuG4nBDD2G/xOQrZtC073jU6Ni/YS8/ndhH89pHJdv3vwiiyDmXT2PR7YsBRRhoZIGtb7/STpaHqwdYjXG4PeIYG72eIMdbHRRlJsRslyWZD/c24w0M59jwBiKkWHUEQhHUoog/aGTrqxtj2nQ1dqJSqyifWk5Pay+uLif7/rkJUZbJvWYeeo+Hl675rSII1IfbX/kJUy6fBrJA3e5mdr9/FEuKkcQ0M+XnFnD9I1cjCAKBYIQjjUpCmygI6DRiXDGjkyHkCTJ0KktTizSFI4TFIQbFKQw0SZKQQpGo1sCY+aVklSXz5gMfEQ5EcHZ60Bk1UeXDwW6goboQR7dso35fXfTvnBF5FE0YqBo5Xbnp4DLDfkQiMj5nfO+VOSHW8Dv0ySFGzh5F44GtWJKseBxuWqu9hINBxswdg6Pdgc6kIxQII6oE5IiM3mLFYLGQWVqCzxMi0htA1dDFOz95ktS81Ki8MsCs6/KJSDZ0RjWiWse4+dMJB8MkZKSTftk4AoJIYmEGKUYt543OOqsLcBZn8b+A/1NGQD8S0xL51bpf88fr/8jx3bFhoK6G4SutwVnyolpkxjVjqJxZyFu/+ziaKJhUnITl3HyCcQR/BiMiCwjlRZSUFVLs9SG5vBz820r2rtp+yuOKxhUPqPeJ/WK+g8coDFvk67VqxhUnD/Hty2iROb7rmFJXj2JIHHe6KBxiBCAKVGUnsK2mE3mI98GsVxOJyLh8fatmrY6iaZUc+0TJCNfotcy8cwn1n+xn8zNro8dpjTr8Tg82kxa/qIjv9Bsy92/+PYXjS/oviBP725TM8j4Pu6gRGbugTOlHMzAetUogGIetMHpv+iYsgJyyFDzOAAl5iQyeMrW+EB/+v+0s+/2FGBP10dW9LxDmg93NFNykUOW+uuAOQr4A+VX5HNt1jPTCdDa9+Pywc446fzxv/b5P+U6rwpadjX3WaLRJOpLMVmRJIts+ncSsyqjXxdVZG2MEyLKSc9B/f87/1tfpOKGU7+WOy0Ick4HcRw0tCAIGtUggIiEgoBLgH5fdTaRPtOWGP32HSZedA8LA67D+7zuYtWxZzLiNlhAr//QC9kl2qvtYAAHKp5ZTu7MWg8WAFJEI9BFvLbrtOhyd0HS0h8ziJI7vaEKtVTH/O1dzfNfeGCPg45fe49xrFmFKzKa72QUkI6jB2Qmp/ggYlPvV6w/R3O0lJ2WYlsxZ/Ivh6nKq1j+33tbd0q1JykwKzf7G7B5LsvWslPB/Mf5PGgEAhaOLuHfNr3js239m/4f74q6i4lHv9iMpJ4Flf7iQLW8epHp3CynzS/DHEfs5GQRBQDAZEU1Gqm6/ivodR09aYgjx1QRPB1EUyEkdXjao7nXx4HUPxGyzJFsYufVhUgfFYF1dXtb+/hNcXV6SchKwlaUQybIg61X4ghHcUqzbfPbvbqJ14c/wdrtY+sJPiWSkMrooi+wJZZgzk9FbjYTMJoIRmR6P4vL+ybZHGVWWgbO9l5SCWOrvjJJk9q4fMNKO7WhmzHz7sPwJwRvC+3E9nh5f9GHpzTqsGWZcHR50ejXhkERimilariZuayShz5uCICD0uWRajnaSnGPFlq0YRB29A6RTkiyj0qjxOTzRCbr1WCuFYwo5vnsgGVgURVLziwn15RmHgxGSxxYwaa5SftlPWn7iye0MroXUGWLDJ+mF2THXGglLUc+RpjKVkFpU6K8BZEXMJxRR3liVKNDdPMigFeSoYQNwfHstNVuPEAnHntPnUbwDg0taYaBqxufykVWaRfPR4aRu4VAEtVZFOBhBAIyJJirPraJ2Zw1+t59wKExvazPWtNhydINZixiSoI8x2qRX0+0OnDUC/pfxzB1PZ6x49N3MoG+A3Of5/3kud+H3F7Vc/8Cys1LC/6X4P117k5CawM/++T881/YCi/pEZFLzU6Nr7NNVdwsqkSmXV/G1n83ky9TQiAY99pmjTtnmdHmDZ8ZL2Nc2TuKDq8uFq8MR02b1Y5txtHuQIjKd9b0cXVPD8Wd34d9Qh+AbXgYZRuDSh5Zz0W+WIWcqctCaJAuGMWVE0lOQEy1RWuF+WFIT0Bp1pBYM1/7IKI5VGWyp7aZ263Da7PCRLtpqunF3+XB3K/86T/QSdPjpON6D3xMiHIzEXHdXgxNXXS/HPq7n2Ed1HP24nsziJD58cTeeXj+1WxsJBsJUH25H5wqg7/Wj7/ER8vex9Q2aUPVmQ0z+Q/aIXEKhWIEyIY5rO8eeSk5ZivLPnoJGFzvx5lVVxvzt6vaSnKWU04mhOE98MEPmkF29rT007K/noa/9hse/+Ucev/ERNjzzel9lwKB7GYxwwQ0X09PWQ1JWEuYkM1qD9hS5LANn8rkC0ZBM8+H97Fq9jQMf7adwVCFl55RjSbawc/UWnJ0uRJVAZnESBqsOKSJT88o+wh/Xo292gTNAVtJXy455FqfGM3c8nfHPB/+RPdgAAAj6guI/H/xH9jN3PP0vkRL+1a9+lVZaWlpZUlJSee+998bNAu3o6FDNmTOn2G63V4wcOXLEtm3boj+27OzskXa7vaK8vLyiqqoqhmzlN7/5TVpBQUHVYDnfDRs2GPtlfMvKyiqee+65xNON/7PPPjOMGTOm3G63V8yePbuku7tbBHjzzTetlZWVI+x2e0VlZeWId955Jy5j28mOP9VY/H6/sGTJkvyCgoKqwsLCymeeeSYRFOKkyZMn20eMGFFht9ujEskHDhzQlZeXVxiNxlML38TB/1lPwGCoNWpu/ONy5n97PkGTCbc/hFGnPuOMfZUUofmF1VinVKEtyDpt+1SrshYcVZiMVi2CAPP+fgvHb17A3bN+FveYkkllp+wznm7BybbHMwIEQcCaNvB7aG7oRZthhuOxyaeyDC1HOinOMEO5UuanUQkkmrT0uIOYRxaTOq6MLneg7/wD99AbiKDzBAkMqkIw9lEU97vEB4/MkmrCaNXFcDN8+sZ+CsZmIfapHep6/ByLYxhYko2nNIxUaoHedk/MNkEAvyvIWw8pZXtl5+Ry5DNFmVClU1F+ZQXXvvZzZCDY0UPtdb8DFElcv8dPUlYS1tQE9CZD1I2v0fjobT1By2cSOkMqrg53NKPfM4QZUasfEDazJFu58mfzeOEXH+DuUWL6QV+YLp+TxDQT4boeGBVrOEmnyGr92y1/xT7ZTvWW6iF7lOejs+pIzEsgFAhzdN0umo/EljL7nEHOv+GbqDUqVJoIomoNGp2G2m07SMpOJRyI4I7oENVa0otLMZjK0RiMeB0eHB0uHJ095FflcfDjA0hSBCmiQhAF1GoRlysAMjQf7ICDHRitOuaeO7yy5Cz+NXB1OVUrHn33lFLCKx59N/Nrd1zebkmyfGVSwtu2bdM/99xzqTt37jyk1+ulmTNn2hcvXuwYOXJkTPb1XXfdlTlq1Cjv2rVra3ft2qW/+eab8z777LPoi7xx48bqzMzMGAvj3XfftaxcuTLx0KFDBwwGg9zU1KQGmDBhgn/fvn0HNRoN9fX1mrFjx1YsWbKkdyj//2DceOONBQ888EDDRRdd5P7jH/+YfM8992Q88sgjzWlpaaGVK1fWFBQUhLZt26a/6KKL7O3t7XvP9PhTjeXOO+/MTE1NDdXV1e2PRCK0t7erAe6+++7Myy67rOeOO+7o2LFjh/7iiy8uveqqq/ZVVlYGDh8+fPCsEfAlkTsij06nnw92N6NVi9jMOpLMWhLNOhLMWrRxsqIb9tfz8NUP0Hi4EdUT7zHr1sWkL5yOEKctKMSF51TEMarVKkonl590bKcrXTzpcXG2mRJNzF0+HykiIUUkQoEQoiiw9a1PufAHlyDLMoe7vYij0ikyajn2QS36RD1yRCLQVxbXtKOZzEIb4vFe1FYtHVmKERwORPAGIthMWno9QTz+UMzk3ryiGmumBUtpMogC4QQ99DEd9hsCAV8AGQFRo6by4nIc/WWZsoxGo0JUC9GVqe9IJ5GwhMaoUchs+sbndfhwdSkJh0arDlEjEgpLZJWloNaoCHqDtB6LNXBMSQbMNj2SJGNLN3N8VwupeYl0nOglY2Q6LqOOfme+wTiw8u8fS3dzN93NCmtiRkkxiAn0tJxg56rNaHQ6tOaBRVY8+1KjGTCOxs0biznRSHJ2QtQIAIUq2NHhwdnlZXplOlK6iZYeH6GIFFPZoo7jeWg/3o4gCFEj8PyfXMmoq87FE5aQZBl/WKLzlbUEfL5hSX1j5s/D2eUnFIig0QTpbunG6/CSZc+mt62b5uoBo2HRbTfh85hIzBxBYt/U0lW/iwMf7aVkYin7163GmGBGrZ1JOAhag5qgL4xIJ0e37SW/qhi/xx+jKHkW/zqsf269bagHYCiCvqC44bn1totvveQrkxLet2+fYdy4cW6LRTEspk2b5nr11VcTR44cGUNLeuTIEf2dd97ZCjB27Fh/Y2OjtqGhQT1YH2AoHn/88dSf/OQnLQaDQQbIzs4OA/SfC8Dn8wlnstCrq6vTL1iwwA2wcOFC57x58+yPPPJI87Rp06JW/Pjx4/3BYFD0+XxC/zlPd/ypxvLyyy+nVFdX7wclob3fyBEEAafTqQLo6elRpaWlfW5Rp6E4awQMgc2sQyUKBMMSbb0+2noHVmtmvZoksw6bRYdZr+Hwmu08/s0/EvQpE08kHGHdQ29Q8vE+Jv70GlSptmH9n2ou//jlD0+674vkBAzG4Ik4Id3GDX+5Oe4+UCSZPX1ljWJ5MiNLk3DKMghgEQS8hzoJtrhofm0/AW8IvUlL3oJS/MkDH+0eT5AUi45eT1DxEvTlAOjMWloOddBySJlgWpKNpP5sJuYkI7XbjrLqT+/wyeubsE+rYPxvloNJo/zrQ2qKCVEUCUcUt3NCaTKCQU1CWQoYtfTuUCajSeeXYErQI0sy1hQjqEXW7mgk0Je3kahREXq/hq6jXYy+oBitXk12eSoqlYrDn53AlmYm6A+jNSg/kaAniHHQfQpptSQXZtB1vDWugfbhc+9SMaOKgx/vH7YPICUnAa1eTdMgfQW9xUhafhrt9e2MWzBBGWeqKYYqLRwKUz4lj9rdzeRkWwlZdYQiEj5PELNFi4BIbooRo1og4cUfIcsyKx5dQUdTF+d9/xIS7LnKE5dlNMlWZLWI1zfwHan5cA8N++uxJFsYPWc8+SNHIQs6HO0Dv4PGg7uj2gS2DBsHPoq9Rq+jlUhAybHRmdIJh9VklFZx4KO9hAMhOhs7obGT0imTsKYm42j3EPLUs/lNpbqk6XADs74+g8kXT457787iq0V3S/cZSQSfabvBOJWU8JgxY3z33ntvdmtrq8pkMslr165NGD16tGdou6qqKt/rr7+eOG/ePPeGDRuMLS0turq6Om2/EXD++eeXCoLAsmXLOn70ox91Ahw7dky/ceNGy913352t0+nkhx56qGHmzJlegPXr15uWL19e0NzcrH3iiSeOn8oLAFBaWup76aWXEq+99treF154Iam1tXWYnvWzzz5rq6io8A41AE53fLyxdHZ2qgBuv/32rE8//dSSn58f+Otf/3oiNzc3fN999zXPmTOn9Kmnnkrz+XziypUrh7r2PjdOawQIgqAHPkJZAqmBN2RZ/oUgCEnAq0ABUAdcKcvyMOICQRDmA4+gUKE9Jcvy/V920P9KqESBqWVpbDrUNmwV7faHcfvDnOhU3tOmbbVRA2AwarYcofHr9zHnrqVYp42OhhVEEcX9HweyLCNoNaSWZEVLBPsNQ0EQQKvB31e/7u91K8Iu8oBrXxGe6RMuEgQsKdaYeu7PkzNQ2+JCJUBEhmBYInqFMmjNWpxlySRUpuJ7v4ZATTd+T5DqNw5QMqcE88g0RJVAICTR3RcSCA0yYDT62FfO1eXl5Z+vw2hx8/ZDr0S3H/n4AGOdHkRrbHJYuK+v/hK+SJYFIdmAE0CSEMcqS8/t3R4utg+wEgZCkagBAOANRcibXUjS3GKKCmxkpyjeCGeHh8OfnaDpaBcZRTZUapGcshRsE7IRDRpCsozbHyYsyVz619vZ9dRKps0fy7TLpnJsVy1/vO5h2uvblbEGB94N++RCsssqsGWYyShOJiHFiCAItNR2oTfr6DjRiyXJwPX3XcrRbdVklSrJc7O/PpZgIMyhT09QOjGbcxaNoPFoF2POL0Zr1qL2RxibmYApQT8s76BqZhU7399JSm4KSeW5mKaNQa1REYpI6DUqPIEwg+9uqKWDhj75YleXi+ScbDy9LmRcaLQgyxoiYTXuHoWdNcueTcgfpHRiKbIkU7OjBoDWmqMc+EipEtHqtUxefC6tdco9Geyt2LFiLWXnVBHw+tm9ZlvM2A9/euisEfC/hKTMpDNaTZ5pu8E4lZTwuHHj/Lfcckvr7Nmz7UajUaqoqPDGYxS89957W5YvX57XFz/3lZeXe9VqtQzwySefHC4oKAg1NTWpZ8+eba+srPQvWLDAHYlEhJ6eHtXu3bsPb9y40bh06dLihoaGfaIoMnv2bE9NTc2BnTt36q+77rrCyy+/3DFU3Ggw/v73v9d973vfy73vvvsy58+f36vRaGLabt++XX/33Xdnr169+ujnPT7eWEKhkNDW1qaZPn26+6mnnmr85S9/mf79738/96233jr+9NNPJy1ZsqTrnnvuaVu3bp3p+uuvL6yurj7Qr1L4RXAmnoAAMFuWZbcgCBpgkyAIq4DLgA9kWb5fEISfAj8F7hh8oCAIKuAxYA7QCGwTBOEdWZaH8SP/OyEzyUhVvo199acmY4qX7NUPv9vHuz/9G2MumkTFD76GaDKycFL+ydsHIzgKC5j3zJ1x93uANTuVVe6OXz/LodWnLil82fvP2LFyZoaAxxeiusmBQatCLQq4/AO/X41KwOVXvgOusIRtbgldx7aCpJThGUqS6PbEGkWSw03dR7twtffid7hJyRyu6BnwhoiEYl9iSZJwbD+EbfaE6DYxHMHT7GT1umMKNbAko6pIAYtuaJcABEMRtBqlX7VKRKsWo6I/VoMab0QmMIRboHxGATqTllV/2RINF2iMGlSzCpB9If5/e+cdHVd1J/7P901T773YqpZ7FcaASwzGBFNSgEBMSAIEEiBAOCFtk+xm89uzSw5JyO6GhCUYQkIWkgDZkGDAYEzApsrGBnfZli3J6lZv0979/TGj0UiakSW5yJLu55w5enPvu2++72ne3O/73m9x2AySY+yIIXTZLaz/+e3MyPJFEsxdNY+H9/6K8g/K2fzkZo58eIQ5K+ZQvHQG13z7mpA+JpmFyQAkpvdHcczw+3+4XV5OHG+naHEWS6+cSXJWHIYhpOUNtTCFIjk7hUtvWcult6zF6zU5XN9BZ6+H1LgIPj7mW7bwdDmJONGDEtj/9OYB4ys/2huY2AfIXJzJtDnTqDlYQ427//uRlJWEI8oxINLG1eviRFUNe7f6bnnT23+9W+tbaKlpwNVrkrfAF/XRVFlHR3MbjZX9xag0Z5aLv3hxy++//7vc4ZYE7JF2c/UXLz6tpYQB7rvvvqb77ruvCeDrX/96dk5OzpCnqqSkJPPZZ589Cr7fhdzc3HklJSVO8JX2BZ+5/4orrmh95513oi+//PLOjIwM17XXXttqGAarV6/uNgxD1dXVWbOysgJf2MWLF/dGRUV5y8rKIleuXBk29e6iRYt6t23bVg7w0UcfOTZt2hRwnjp8+LDt2muvLdqwYUPFnDlzQmaSG258KFmWL1/eHRERYd50002tAF/4whean3rqqRSAp556KuXll18+CLBmzZoup9Np1NXVWfuWO8bCSaMDlI++u9rmfyngU8CT/vYngU+HGL4UOKSUOqKUcgHP+Med8yTGhJ5YghnJetLOF9/npS8/QM+eocmFzgQzL5zlT9c7cMrvW28P9Qqmym/l6HF56XR6SPFPsNEOC1aLgTMoA5/La5IxL4OsxZnMvWMppsOC3SIYQGp8BNnJUfQcqeHln/yJPS++T09DK15nJxajheN730bMRiwWL0qZ9HbUMX3eQCXpoz+9QePGt7FWNWPb38SxJ3bS/E41h8qOc2RHDVU7a3E2hE+bvaviBF6/5UApxby8RCLsBjNz4lk+N5OVczNJj4+kqa0Xj9fk0PE22nvcFJRmM2dFXuA4BZcWYjUgJdZBtMOKV0FTu5MelxeH2xxQdKqlvofK/T3MXrmSb/7hB/zHmz/hlp/eOqq00H3Y7BYy8hMpWZpLak78KSXPsVgMZmTFM3daAilxDuZOSyQ1zkFvZTsHn93Dsb8doOz5bQPGWB39ZlJ7hJ1FaxeRNz+PnJm5GBYDj3vg705zTTP1FfVEx0dTECJlNPjKHJcsK6H4vGIWrV1EUk4a0xZcyLT5F5A7bxmX330j6+7+AglZ02lrHGIZ1pwBYpPjvFfefVXtcPtcefdVtWNxCgwuJRyqv89hr7y83P7iiy8m3HrrrUO0v6amJktvb68APPTQQylLly7tSEpKMtvb242WlhYDoL293diyZUvc/PnzewCuuuqq1tdeey0WfBOv2+02MjIyPPv377e7/bG7Bw8etFdUVEQUFxe7wFdKeMuWLUPCUvpk9Hq9/Mu//Evmrbfe2tAn17p164p/9KMfVa8u/KJvAAAgAElEQVRduzbslzXc+HCyGIbBJZdc0vbiiy/GAmzcuDGuuLi4ByArK8u1cePGOIAdO3ZEuFwuGewUOVpG5BPgf6LfDhQBDyul3hORdKVULYBSqlZEQoV3ZANVQe+rgQlh4xsu+Uwfw1kCgmmpbeG5r/6C4vQYFl9x3snj/cZISk4KP/7HA75lAUJEBQRtD+4XoKXTSXtXvzKrFDR1OIk70UPNO1UkrimAIHN+pN1C7Ko8Opwe2txecHtZVpJKWmL/fbS4aA2q8jj/9+DzRCVEsXfrc4G+w9vLiYiOIC0/g8rdR5l1UX8ZY/A5XVbtPsanv/U1mv1ldmvKT5BVnIyrx01KTjyrr5+PEaKYgmkqDEMw/NfaZjHITokJmP0BrBY4f1Yar+88zivbq/GaClutQVykjd55qZQsSCcSaPV4USZ0Oj30urwkxTjAa2K+d5w/f1zGvBsXYEmPpvFvB5g+P4PU/ATScxLInZWKx/RlchyueNTZpM+5NT0hkh6Xl2N238Odq9fDFfd8gYoPP/RHLwBKsWjtIlrrW7ns9k+y7s4rAsdprGzgmR8/w+YnXsP0+0SICKbXDDz1z14+m71b9+L130vJWckc+fAIbqebvPl5lH9QTmZRDpkz+xUGqy2SztYuwGT3W0e56LMDwyQ1Z4a+PACD8wTYI+3mqeYJGK6U8NVXX13Y2tpqtVqt6he/+EVlamqqFwaWEt65c2fErbfemm8YhiouLu79wx/+cBSgurra+pnPfKYIwOv1yjXXXHPi2muvbQe45557mq6//vq84uLiOTabzXz00UcrDMNg8+bNMVdeeWWm1WpVhmGon/3sZ5V9k2i4UsKPP/540oYNG9IA1q1b13LPPfec8MuYVllZ6XjggQeyHnjggSyAzZs3H8zOzvZcf/310++6667GlStXdocbP5wsP//5z6vXr1+ff//991uSk5M9v/vd744CPPTQQ1W33XZb3sMPP5wuIjzyyCNHjVNJIw/IyarmDdhZJAH4C3A3sFUplRDU16KUShy0/3XAZUqpr/jf3wQsVUrdHeLYtwO3A0ybNm3JsWOntWTyqOnocfPKh8eHDbuq//tWXn3gj6M67sU3r+G2h+/EYh+of/U4Pbz6YcgKk0MItxxw2R3ruPW/vxZ4H2raCXc2xxs7+fDICUwFCdF2up1unG4T274mKrZVokxFRIyd3KtKcMY5iLBbEHwWg8CxlcL6UT2llxaTXtAf4//Ibf/F60+8xpyVc4c4kQ1mzVfWEhUXhc1hw2q3YbVbKVgyj23P7gvUEgAoWpzFVfdeiPskqXRHwqbt1fS6vYhAQpSdrl43xdnxFGbFs+9YC+W1vvTSM3PiSY2PxOn20uvyUPbYduoON1O4tgiKEolymliq2rnwc/MwLILdEFx+JcBuyJisAWeaPe9X8dIv++sH7H/rZRqO9jtnF5UWc+mtl5K/IJ+ZF8waMr76QDU/W/8gh3ccxmKzYLFaBvjJrP7ixXz6O9dy/+J7KV46lxkXLMXV1cKW3/2dno5uis4rIWvmsgHHjIp30N3mJDk7jpv/47IzcNaTExHZrpQqDW7btWvX0QULFjSFGzOYjuYOY0tQxsDVX7y4ZSwWgGC2bdsW+eCDD2YMV953vGlubjZuvPHGvIlWSXAwUVFRi7q7uz8c3L5r166UBQsW5IUaM6roAKVUq4i8AXwSqBeRTL8VIBNoCDGkGsgNep8DDE015jv2o8CjAKWlpaf+y36KxEbaWFqcwrsHG8PvNJrKf1YLKbkpVOw4jM0iWA3BG6Rg2CxClMOC4HMI7HJ6TlrMaDBzV88b0nay3AFKKQ5UtnKwtr+OQmuXi+gIK7ERNtyFSai3fApZb6eLI3/eQ8lnZuNKiwqsr/cdx7Krnor3qql4t5rCxVksX7+A2OQo1n71cl5/4jV6OrqJT42nrTF0ZsSYxBjS8tL59HevG2Kp2PNmFU1V/eMWrS0eUKNBgv6Gy/IYCgFWzknn42MtON1eWrtcmAqONXSSkRRFUbbPDH+kvoPirHhcXpNE/xJJzv0r+NsvtnF40yFmRJRAajQ7N5WDUsxdXUBCpi9s0nIOTv59ZOcnkb8ggyM7a7E7nDQfP4E9ws6clXP47HeuZf7q+cMqLzklOdzz+L3ct/gbeN1eFq5ZSEdzR2D96eJbLiWzJIfHan7Pk9/eRHNtLxDJ8vXriYi20lZb4UscVJSMq9uN6fUpm91tTno7XfR2uYiIHuKMrTlDxCbFmmMJAxwOXUr4zLNnzx7HNddcU5icnDxq582RRAekAm6/AhAJrAF+ArwAfAl4wP/3ryGGfwAUi0g+cBy4AVg/WiHHi5yUaCKPNg942g1mpE920+fl8YMXfkjqtFSCTTdG0Hibw+DK0mmBJDPbDzdxuG6I9WxYouJGlmVN4Zu0P9p8mI82HaK73UlMciQpCzJwTovHZhEi7Raa2p3gsDDvlsWUP7+XSIcVR7SNvX/eTcFF05DZKSjDQCmFbU8TR97rT9pzeEcNVpuFi79SSs7sXAqXFHF4+yEc0Q7mrJxL+QcHh0RWTJ+Xx8wLZmIzfMsZrqBJ3uEvhmRzWEjMiOXDV8u5ojgZq9UY8n9QSqEAr1KYKrxCYBGwiuCIdjA7N4Foh4191a2U17bT2esJ5ItIi48gJc7Btr31mEqxtCSVCLsVa4SVT31rBQ2Hm2kwTZq6XIgBO189xM5XD3H1vReSkZdIRPK5m/3OajPIm5tBXJJJd2sLl275d4qWFGGPGPnEm7+ggO//9Qc895NnaaxspHJPZaCvLxW0IyZiQIEnZ7cbZ7ebtMIS2k/0cPxA/8Nqkl956mrrpeyl/Sy/dvhsmpphMU3TFMMwxvXBSpcSPrP0JQsK1WeapkD4pLYjUcsygSf9fgEG8Cel1N9F5B3gTyJyK1AJXAcgIln4QgHXKaU8IvJ14BV8IYKPK6X2jObkxhNDhIvnZbJ1XwNt3UNDAUeqBHz1l18jPURa3FD0HXNJYQp2q4V91a1D9rn7f+7yre2KIIZvTGdnL/sbenh3Xz1KgUIFLAkxEVZ6XN5A/fmkWAcut0l1TXvA+aqlpgOP00thbjwtpvIpAH7aLBCfEkX9oX6fnd7GLq4uXezP3KfY2exksBp9aGcNMbtqiIkQav255p1dTva8uZuouChmLZ9L6vR8ulpaefcvWzhR08oC/5On2zQRfKGAx/c2kFmYzJyLpjPz/FzskcPH9YrfJ6JPyTL9ykCf5aXfCgIe02TrvgZiI23MyIpjdm4CTe29gbwGUQ4rnb0eWoOiHt7aXceS4hSSYiMwLAYZM1KQlm6OHmgk/xMFHHn9CFnFyaRPSyAyxo5pmpzqut2ZIiYhkiWXFQNDozZGw3lXLiUyLop/WvXdQJvVbuXdZ7eSlJVM/uKiMCOF3s6B91Z3ezsNh7bj7O7l4LZXOP/qX2OzjzpEXeNjd2Nj4+zU1NS28VYENGcf0zSlsbExHgi7DntSJUAp9REwJBWhUuoEcEmI9hpgXdD7jcDGEcp8zhEdYWPNgixqW7rZXdlCe3eQtWUEOsCMpTOYu3LumD67ODMOm9WgqrEzMCkBJGUmDfEUd8RH015bTXvb0LKxIgyY1Ov86Wotg06g40Q3H23YTuGlRTin91cUtO5tovLQQKfdotJsrPa+sD5h8RUlJKTHsPHh/vXl2TcvptHtpd0UersGytXd3k1schouZwzphZl86v5iWup7ef/vBzj/qpngNvl4yxGi4yNIyY2n+IaxPw0aIhgCVvrLLvuui+/90uIUDta0Y7UYOGwWVszOQAQqGzvZU9U6YNkDfH4Q2/bWs6wkjdQEX4KkjIRIVs/N4GCnG1dJCtUHmnjup29x+e3nBcIAJztFS4pYccNK3nrmTQCmz53Ok/c/Dviudc6saRQvXURidjY2h4PWhk4Mq5A9IwWlFO1NXXS29OJ2mhx8b1/A4fDVDa+y7o51YT9XEx6Px/OVurq6x+rq6uYyxWvFTFFMYLfH4/lKuB3OzQWacwyLIeQkRxPtsLJtfwPdTl9Ehozg6W7F51eN+XMj7BZmZseTnxbDpp3HA8sSo3U0lzDaSihDhmkqlq7Mw+mwopSi8Ugzb26rHLKfI2qoubigNJtLbl7M5id3UHLVTNr8k63Tq1j/++/y1Pp/D+z7yTtvwOWMwuPy0lzbQXJ2HMrsYefrh1lyWTFV+xtZeHFhkKJx+hhswYmOsLGooH+ijvB/ZlFmHF6l2FfViogM8oGAdw80BBQBJUJ8jIOLLpuB6xMF/PHf36C+ooVNT2xn+px05q3MJyE9BkuYZFGTgYjoCL719LcpWFjAUz/8/QDFTylF1d5jVO09xqe//VUaKzuJTY6iuaYDl78YVVpeAl6vwhBY+7VrePlXfwag6Xj4MFDN8CxZsqQBuHq85dCcu2glYBQkxjj45KJsGtt6aezo5R3v8GGE0QnRXHn3laf8uQ6bhU8uymF3ZQuREZZRhxiGtQGGCK277vufIDEzLvA+IymK6sXVHNnhM+fHJUdhi7Dy2oYy7BFW8pcMLAvrdliZfef5dA4KsZTpmVz/q3txVjhBFG2NLrye/n3cLg+OKBvLr5nDgQ+qmTYr9YwoAKOhprmbupYe5uQmYgIut4dDtR14TEVeWgx5aTHYrUbAGbHPAdDusHLlncvo7XTy4q/fY/sr5ZS9dJD41GiWXjmTBasnd3Gcz377GhB48ju/HXa/vtoOfTQcbSV3dhpVexuITkhk/pqlgOBxO6g70kxGQVLoA2k0mjEzqhDBs0VpaakqKxs+I965QGtDK8/8+Gn2vrWHox8dHdAXlxLHHb++k6VXnY/NcfrWM92miTfEv8zt8fJS2dBqegApcY4BywF9JMXY8XZ76Klo4dibR1l5/TxmrcwfsE+Py8NHh05Q8bf9OLyKuqMteP0JgyxWg6vvu4js2f0pInqcHv7xce0QE7q3uY3yDRuJigu99jxzWS6zl+dhsQi5s9JOKTHO6cRUirYuly/lsFKU17bT4/JyyfysQOy/VyncpsL0moGqiH0opdj1+hHef3E/3e1OImPsfPabK0jNjQ/1cZMGpRSPfeNRasprqNhZQXOtbznp6vu/Rne7O/D0P5isomQMi1B9YGBUW87MVK7/3qpzMszyXCFUiKBGczK0JeAUSEhL4Gu/vAOlFL++81ds+9NWOpo7MAyDbz3zbRZcsvC0f6ZVhGMf11F3pBllKkyvb/KxxtlJyY3v91MIqs1rsxiBzH/BmErRZpowPZ6cLy0kZvrAJ60TbT2UlTfh9ioS8xIpH5Qa2+sxKa9uxZ4ZS0pCBCJCpMPK2sU5vLu/nqZ2J2ZPL7UvvMW2x17G1ePikltycLsHVoezRVhZef08ouIisNrG9+l/MIbIgOyRqfGRGDJwScEAHIYglqG3k4iw4OICHFE2Go61YnrNkMmNJhsiwm3/+VUA3n5uG7+4+T8RQ3CkxNBaP1zYLVQfaCKrKImaID+U6v2NHHy/mpLzc8OP1Wg0o0ZbAk4jXq+XD/7uK4Sy7FPLTrL32Hn3hX1sfXags2dGSQr2T+SF3D81zkFjKEtAtH1Irv+F+UnkpsVwqKaNff64/DiLwe5H3h8QZBKVEknB5+bR7k/LOysnnuKcoJTYSvHib1/n+e89QUdTfw6CkmWzyJkzH7fLX4pXYMW183zOgJMUpRT1FS1kFCShTDXiTJOTBVevi60HGmn252CIsRpEVLZjMwSloKu1m8bKdgSFxWqh5pAvmiyzKInaIEUgMSOGa7+1gvjUmHAfNaXRlgDNWNCWgNOIxWI5o5N/HwsvKaS8rJr6o0Hhg8Moc+F6QrXvrGimucMZqJQI0O41mXdrKb27allz63m0NnRS5/RwuLG/UMyB420kxDgC3vKIcPjVHQMUAIAD7+6jen8Vy665koSMNNbeXEpm4eRe6xWRwHr2VFMAwFd3YPmcDF543+dg2ukxSV2Uwby8JBChs6mb337rpRDjbNgcFtz+TJEtdZ3sf6+a8y6fgWGZvA6WGs3ZRCsBE5CIaDsLLynilQ391pLhDDrhogPCNSulKMqM41BQFsFZs1JJu2A6CCTnxJMM1LX30uWPlDAVHK5r71cCgK72LkSEYGvT9LnTiU2OZfYF6Vz8pUsmtbe8pp+uXg/BGZ6P1XcyIzsBh91CsDqanp+A1WbFajc4trsee6SVxIwYDKtBZLQdj8uL2+XFEam/NxrN6UArAROUkvNzee9v+2ht8D2x2+wWpoUxk0baDF/Ym/RVDvTN/hF2g7jI/lA/p9vLtLQYMpKiqGzoJNphJcJuoTAzdkBRoABBSkRmUiSFGXH0/aQLsPqmSzhR1URjZSPZJTnc/ODNFCwqJCouSjt4TTHiIq3s+OfHUF6TzIWFFC4uxLHMVzUyWH+12a1UH2gie0YKMUmRdDb34OrpJLMwifambrrbenn637bwiRvmkzcvY3xORqOZRGglYIJij7By3XdW8fh3XsbrMUnJimVhmKQ0oSoKDtcOEGEz6HJ66HJ6cHtNYiPtRDisAY94ZSoiWp30Gor5M9PJTfMpICZg9TvOfWL9KqbPySU5O5neLiep01L15D9FOVF9gr2v7wJg3z8+ZosIxz84wNo71hGblDhk/+MHm3xFmCKtKBMaKltJz0vk+KETrLhuHnUVLWTPSMHm0D9hGs2poO+gCUx8ajQFCzMpLzs+6mJDoQhWClITIklPiKC+tZf2bjebd9WQnx7LzNwEnB1ONj/2AVV7GzEsQk9+IrFfXkJCdhwK8Ciw+ef66fPyMAyDuJRTl08zcWmsOsGs5Yuw2CwYFgsJacnYI7PY+0YF5392qBIAYHrVgFDClvpOejqctNR2oIDm2g7S80KP1Wg0I0MrAROcBasLTpsSEIyIMHtaIg2ttQHFoKK+g5qqVqr+vCeQ7930KuoONbN94wEuu/08TOXLaKiUApFzNme+5uxSU95Nav7A1M/NdU6Sc2y01nWSXZICShEzLYHiIr+jqL/4kyhfHQwROPpGBfVHW1h7yxJtBdBoTgP6LprgTJ+T7stAN4yVfaz6QWyUnYKM2EA1Q6UU7W8dCygAMy+cxqqbFmK1WTAsBhYRFL4qiOdy+VzN2cXZ42bv28dC9nW39fLXn28NvM+Kc5B8XjZtZuhvbVRKNMf21tPb6dJKgEZzGtB30QRHDGHNlxdjAu4wP5zDrf2fjFnTEpieFktbtwtxe2ldrkhMi2H/25XEJUZiEQOrP1zLq3zJjPS6vyaY2uZuir8wjy3f/c2QPoN8oH+tqKasho7j7eRcM4cu99C03BabQVubk/IdNSy+NFxlQo1GM1K0EjAJEBGfV76AJ8y6QLhpeXB7T6+bfVVtgfIEsZFWirITiInypT7Oyoxj9qp8Vt24EEe0LTDeAudMql/NuUWz04PbZlC5p4Kejp4BfTFJ8UQnD3QY6ajtpO3tShIvyKVjUPrp7mbf+LKXDrBgdYEOMdVoThF9B00irIZgDfEUrsK8QvU5PSbVJ7qoauqiqr6T8o0H+WhTOc3HByb9sUXZQPm+QBa99q8ZhpZOJ6lJsfzbGw+QPSOb+NTgugmhldaaHbXQOjTLpenPUNne1M2+d4ZWt9RoNKPjpL/cIpIrIltEZJ+I7BGRe/3tfxSRnf7XURHZGWb8URH52L/fxMsFPME4nWnpI5q6Ofx2FW8+/RHP/MtrvPrI+xzcdgybX9lQTg+m28TQ5n/NMCgF1Se6iM3P5K5Hv07JspIBfeHY/+xu4pyDqlF6m7EYLVikhbeffWNAJUqNRjN6RrIc4AG+qZTaISKxwHYReVUpdX3fDiLyM6BtmGOsVko1DdOvOU2ICA4DnGH8A0aF2W+KNb2KA+9VcfD9KvAqZl6QS/3RVqYFVRDUaEKxqCCZow0dpMZHUtnezY6Xd/R3DqMFeJ1edv/2Q+bctTSwLLDt6Zfobu8vQZwzM411d6w7Y7JrNJOdkyoBSqlaoNa/3SEi+4BsYC+A+LzAPgdcfAbl1IyCPkXAq8L7CJyMxGg7xvQEEqbH03qsX79TCspePkhEjJ28uemnS2TNJCY51kFyrIO2pk72vN3AFd+4OeBLklGUQumV8/zOKYIIbPr1Sxx4d1/Ao3X/hka62nwRKvmLCtjzj/7iWZufeFUrARrNKTAqx0ARyQMWAe8FNa8A6pVS5aHG4Fv02yQiCvgfpdSjY5BTM0pEBKv4Mvt5x6AHWC1CY5eXlCtKyAwsGvnqABSmxRCbEIk90nY6RdZMYtqaunj3r/tx9Qgdzf2VK9MLrMSlD0x3feJ4HTs3+apxJqQlsOcfO3E73YH+3Nm5uHvdGBaDns5e6o7UklGQeXZORKOZZIzYm0tEYoDngG8opYK9xD4PPD3M0IuUUouBy4G7RGRlmOPfLiJlIlLW2DhMvXHNqAhbPGiEuLwmbgVdbhOXqejxmGCzkB5UKEijORmOSBs9HT30dLRjtbmxWH1r+aEMVcGVFlsbWplx/gzsQTUumqqaqDtSR015DdX7qtj02KYzLr9GM1kRNQJzsYjYgL8Dryilfh7UbgWOA0uUUtUjOM6PgE6l1E+H26+0tFSVlWkfwtOBy2tihukLlT+gtdPJm7vrSI1z0NjuxGpAZlIUWUnRCOD2miRE+8y7Gs1oaDjWwFfybgGgcEkx2bMvZNqcNCKi7Xg9Jh63FxGhZv8+XtvwwoCxs5fPZu/WvQBEREfQ29Ub6EtIT+Dxqt9itU3tiGcR2a6UKh1vOTQTi5PeNf41/w3AvmAFwM8aYH84BUBEogHD70sQDawFfnyKMmtGgc0QXKYKGYgV3NanECTEOLjaX93N7fZitRqB5D9G30sHA2jGQHxaPBd89kLeef5t2hpayS5poKfdTuUe14D9rINi/2deOAvDYjDrolns27YPR7SDgsWFvk6lSMhIpGxjGcs+texsnYpGM2kYiep8EXAT8HFQGOA/KaU2AjcwaClARLKAx5RS64B04C/+ScQK/K9S6uXTJbzm5Piu/dicA+02y4CRwRUCNZrR4oh0sP5f1/PO82/TVNXIm//7EivXrwNL6sAdg6yTc1bOpWLnkUBEQHRCNDklOex5a8+AIXvf2sPcVXOJSQhdTluj0YRmJNEBWwmTcE4p9eUQbTXAOv/2EWDBqYmoOVUM4aTOgSNRE/TUrzlVps/No3RdKU3VJ3BEO2g+XkXqdDfHD1T59xDi0+LJX5iPMhVHPjwcyDJoi7BRsLiIg+8exBE10Celp8PJB38vY/UXPnF2T0ijmeBM7UW0KYJVfF794XwDRnwc41TdDDUauOuxu/ne8u+w/PrlWG1WfvvtJwb0z7poNhU7K4aMy52dT2L2Qs6/ZmHI43q9cWdEXo1mMqOVgCmAiGAblEBoJJP5YOuAx1TYDfGVdz2dAmqmFMmZyfzk7Qc5uquC+qMNZM3IHtDviHaQNz8P01SgFMpfSjg+NXbY45ZvP86aLy0+k6JrNJMOrQRMEXzlfX05A8J5CZzMe0CdpF+jGSmJ6Yl05HSw67Vd1Bw8TlZxFnWH6zBNk/jUeI5+dHTImJbaZpZcNTfsMXUBK41m9GglYAph4HPuCzeRD24P9ZNqKoXh31c7CGpOhfT8dHLn5JI6LZXu9m7MQJrq0N/QvuJB4YiM0WGrGs1o0aXfphCGCOHm7XDT+dDKg7pioOb04Ih0MHfVPO585C5cPf1hgo7oCPIW5DN9Xh7pBRmB9pMpAREx9mH7NRrNUPSv+RRCRLCJYDMEQwZO/OHyCLjdXt/aLD4rQH1rDy6PSbfTczZE1kxy0vPSWXJ5KZ//1xsBmLNyDvu27uXorgqOfXwUe0R/amqP24PPlmXiy0JugpiImKBMIqK1EqDRjBa9HDDFEBEsgMUfMeDum+DD7L/7WAvVTV1ER1gxFXQ7Payem0mk3XLWZNZMflbcsIK/PvR/VOyswNntDLTHJsWSv7AAMQSL1cKbv/99yPEWq4WE1KuAC86SxBrN5EArAVMYEcFu8SkDCp/3/2BloM9ZsLO3/8n/RKeT3OQolFLaL0BzWkjKSMLd6x5QJhgIpAoG30QfDq/HS3J20hmTT6OZrOjlAA0iguFfJuhbKjAELAJJsQ4Sou1YgjyvPz7WjNurMHWogOY0cvGXhq9G7vV4h+1Pm552OsXRaKYE2hKgCRC8VNDHjKx4ZmTF09LpZMvuWjxeRVykjRMdvcRH6VLCmtPH9T+8gRd/+SKuHmfYfWZdOAsExPDHqKj+7ZyZuWdNVo1msqCVAM2ISIxxsHZhNvur21hUkDzAMqDRnA6iYqN4tvs53E437/zlbV5+5CV2/2P3gH3KPyj3OwgOJCUnhWlzpp0tUTWaSYNWAjQjJibCRmlRyniLoZnk2Bw2Vt6wipU3rGLHqwd45y/7A6Esm3/zRMgxK9ev0v4pGs0Y0EqARqM5Z+np8NDT2Z9DQKmhjijT5+Xx+R+tP5tiaTSTBu0YqNFozlmKSwfWFQihA7DuznU4InW2QI1mLGglQKPRnLOkTUsgqyg58D6UJcCw6J8xjWasnPTuEZFcEdkiIvtEZI+I3Otv/5GIHBeRnf7XujDjPykiB0TkkIh893SfgEajmdx88rbzWHHdXGwOC5mFOQP67JEOknOyQyoHGo3m5IzEJ8ADfFMptUNEYoHtIvKqv+8hpdRPww0UEQvwMHApUA18ICIvKKX2hhuj0Wg0wfztl+8wd0UeM5dN49guXzlhm8NG2vQ0Lrl5DTGJCdopUKMZIydVApRStUCtf7tDRPYB2cOPCrAUOKSUOgIgIs8AnwK0EqDRaEbE8mvnUrm3gdyZqXzvufs5+tFRZiydQVxKHAfeq6JocdZ4i6jRTFhGFR0gInnAIuA94CLg6yLyRaAMn7WgZdCQbKAq6H01cP5YhdVoNFOPwkVZFC7qn+hTc1MBaK7toLg0B3IGvjcAAAWnSURBVItV+wRoNGNlxHePiMQAzwHfUEq1A78GCoGF+CwFPws1LERbyMU7EbldRMpEpKyxsXGkYmk0mimIMhWJGTFaAdBoTpER3UEiYsOnAPxBKfU8gFKqXinlVUqZwG/wmf4HUw0E5/LMAWpCfYZS6lGlVKlSqjQ1NXU056DRaKYYYoj2A9BoTgMjiQ4QYAOwTyn186D2zKDdPgPsHjwW+AAoFpF8EbEDNwAvnJrIGo1Go9FoTgcj8Qm4CLgJ+FhEdvrb/gn4vIgsxGfePwp8FUBEsoDHlFLrlFIeEfk68ApgAR5XSu05zeeg0Wg0Go1mDIwkOmArodf2N4bZvwZYF/R+Y7h9NRqNRqPRjB/aq0aj0Wg0mimKVgI0Go1Go5miaCVAo9FoNJopipyLObdFpBE4Nk4fnwI0jdNnnw60/OOLln98mejyw9jPYbpSSsdXa0bFOakEjCciUqaUKh1vOcaKln980fKPLxNdfpgc56CZOOjlAI1Go9FopihaCdBoNBqNZoqilYChPDreApwiWv7xRcs/vkx0+WFynINmgqB9AjQajUajmaJoS4BGo9FoNFOUKasEiMh1IrJHREwRKR3U9z0ROSQiB0TksqD2N/xtO/2vtLMveUCWsci/REQ+9vf9l5xDZdhEZIGIvOOX728iEudvzxORnqBr/sh4yxqKcPL7+0L+P84lRGShiLzrv8ZlIrLU3z5Rrn9I+f19E+H6/zHoGh/tq9MyUa6/ZgKjlJqSL2AWUAK8AZQGtc8GdgEOIB84DFj8fQP2nYDyvw9cgK8WxEvA5eN9HkFyfwCs8m/fAvw//3YesHu85TsF+cP+P86lF7Cp7/uAr/bHGxPs+oeTf0Jc/0Hn8jPgnyfS9devifuaspYApdQ+pdSBEF2fAp5RSjmVUhXAIWBpiP3GldHK7y/9HKeUekcppYDfAZ8+iyKfjBLgTf/2q8A14yjLWAgn/4T4PuGrBtpnvYgHasZRlrEQTv6Jcv2BQOn2zwFPj7csmqnBlFUChiEbqAp6X+1v6+MJv1nuh+eSOT2IcPJn+7cHt58r7Aau9m9fB+QG9eWLyIci8g8RWXH2RRsR4eQ/2ffpXOEbwIMiUgX8FPheUN9EuP7h5J8o17+PFUC9Uqo8qG0iXH/NBOWkpYQnMiLyGpARouv7Sqm/hhsWoq0vhOJGpdRxEYkFngNuwvdEfUY4zfIPd15nheHOB58J/b9E5J+BFwCXv68WmKaUOiEiS4D/E5E5Sqn2syJ0EGOUf9yvex8nkf8S4D6l1HMi8jlgA7CGiXP9w8k/Ia5/0P38eQZaAc6Z66+ZnExqJUAptWYMw6oZ+BSag9+0qJQ67v/bISL/i8+seMaUgNMsf7V/e3D7WWME57MWQERmAFf4xzgBp397u4gcBmYAZWdQ1JCMRX6G+T6dbYaTX0R+B9zrf/tn4DH/mAlx/cPJzwS5/gAiYgU+CywJGnPOXH/N5EQvBwzlBeAGEXGISD5QDLwvIlYRSQEQERtwJT4T8LlGSPmVUrVAh4gs8y9jfBEIZ0046/RFWoiIAfwAeMT/PlVELP7tAnznc2S85AxHOPkJ8/8YHymHpQZY5d++GCiHiXP9CSM/E+f6g89ysV8pFVi2m0DXXzNBmdSWgOEQkc8A/w2kAi+KyE6l1GVKqT0i8idgL+AB7lJKeUUkGnjFrwBYgNeA30wU+f3D7gB+C0Tiiw546exLHpbPi8hd/u3ngSf82yuBH4uIB/ACX1NKNY+HgCchpPwn+X+cS9wG/Kf/abQXuN3fPlGuf0j5J9D1B7iBoQ6BE+X6ayYoOmOgRqPRaDRTFL0coNFoNBrNFEUrARqNRqPRTFG0EqDRaDQazRRFKwEajUaj0UxRtBKg0Wg0Gs0URSsBGo1Go9FMUbQSoNFoNBrNFEUrARqNRqPRTFH+P9UVQz8R+IWhAAAAAElFTkSuQmCC\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "ax = df.plot(column='HR60', scheme='QUANTILES', k=4, \\\n", + " cmap='BuPu', legend=True,\n", + " legend_kwds={'loc': 'center left', 'bbox_to_anchor':(1,0.5), 'fmt':\"{:.4f}\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "ax = df.plot(column='HR60', scheme='QUANTILES', k=4, \\\n", + " cmap='BuPu', legend=True,\n", + " legend_kwds={'loc': 'center left', 'bbox_to_anchor':(1,0.5), 'fmt':\"{:.0f}\"})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The new legends_kwds arg `fmt` takes a string to set the numerical formatting." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## When first class lower bound < y.min()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "ax = df.plot(column='HR60', scheme='BoxPlot', \\\n", + " cmap='BuPu', legend=True,\n", + " legend_kwds={'loc': 'center left', 'bbox_to_anchor':(1,0.5),\n", + " 'fmt': \"{:.0f}\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "BoxPlot \n", + "\n", + " Interval Count\n", + "----------------------\n", + "( -inf, -6.90] | 0\n", + "(-6.90, 3.21] | 353\n", + "( 3.21, 6.25] | 353\n", + "( 6.25, 9.96] | 353\n", + "( 9.96, 20.07] | 311\n", + "(20.07, 92.94] | 42" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bp = mapclassify.BoxPlot(df.HR60)\n", + "bp\n" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['(-inf, -7]',\n", + " '( -7, 3]',\n", + " '( 3, 6]',\n", + " '( 6, 10]',\n", + " '( 10, 20]',\n", + " '( 20, 93]']" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bp.get_legend_classes(fmt=\"{:.0f}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In some classifiers the user should be aware that the lower (upper) bound of the first (last) interval is not equal to the minimum (maximum) of the attribute values. This is useful to detect extreme values and highly skewed distributions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Categorical Data" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "ax = df.plot(column='STATE_NAME', categorical=True, legend=True, \\\n", + " legend_kwds={'loc': 'center left', 'bbox_to_anchor':(1,0.5),\n", + " 'fmt': \"{:.0f}\"}) # fmt is ignored for categorical data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff -Nru python-geopandas-0.7.0/examples/plotting_basemap_background.py python-geopandas-0.8.1/examples/plotting_basemap_background.py --- python-geopandas-0.7.0/examples/plotting_basemap_background.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/examples/plotting_basemap_background.py 2020-07-15 17:54:36.000000000 +0000 @@ -4,7 +4,7 @@ This example shows how you can add a background basemap to plots created with the geopandas ``.plot()`` method. This makes use of the -`contextily `__ package to retrieve +`contextily `__ package to retrieve web map tiles from several sources (OpenStreetMap, Stamen). """ diff -Nru python-geopandas-0.7.0/geopandas/array.py python-geopandas-0.8.1/geopandas/array.py --- python-geopandas-0.7.0/geopandas/array.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/array.py 2020-07-15 17:54:36.000000000 +0000 @@ -1,6 +1,8 @@ +from collections.abc import Iterable import numbers import operator import warnings +import inspect import numpy as np import pandas as pd @@ -12,10 +14,15 @@ from shapely.geometry.base import BaseGeometry import shapely.ops import shapely.wkt +from pyproj import CRS -from collections.abc import Iterable +try: + import pygeos +except ImportError: + geos = None -from ._compat import PANDAS_GE_024, PANDAS_GE_10 +from . import _compat as compat +from . import _vectorized as vectorized class GeometryDtype(ExtensionDtype): @@ -41,7 +48,7 @@ return GeometryArray -if PANDAS_GE_024: +if compat.PANDAS_GE_024: from pandas.api.extensions import register_extension_dtype register_extension_dtype(GeometryDtype) @@ -49,7 +56,7 @@ def _isna(value): """ - Check if scalar value is NA-like (None or np.nan). + Check if scalar value is NA-like (None, np.nan or pd.NA). Custom version that only works for scalars (returning True or False), as `pd.isna` also works for array-like input returning a boolean array. @@ -58,40 +65,105 @@ return True elif isinstance(value, float) and np.isnan(value): return True + elif compat.PANDAS_GE_10 and value is pd.NA: + return True else: return False +def _check_crs(left, right, allow_none=False): + """ + Check if the projection of both arrays is the same. + + If allow_none is True, empty CRS is treated as the same. + """ + if allow_none: + if not left.crs or not right.crs: + return True + if not left.crs == right.crs: + return False + return True + + +def _crs_mismatch_warn(left, right, stacklevel=3): + """ + Raise a CRS mismatch warning with the information on the assigned CRS. + """ + if left.crs: + left_srs = left.crs.to_string() + left_srs = left_srs if len(left_srs) <= 50 else " ".join([left_srs[:50], "..."]) + else: + left_srs = None + + if right.crs: + right_srs = right.crs.to_string() + right_srs = ( + right_srs if len(right_srs) <= 50 else " ".join([right_srs[:50], "..."]) + ) + else: + right_srs = None + + warnings.warn( + "CRS mismatch between the CRS of left geometries " + "and the CRS of right geometries.\n" + "Use `to_crs()` to reproject one of " + "the input geometries to match the CRS of the other.\n\n" + "Left CRS: {0}\n" + "Right CRS: {1}\n".format(left_srs, right_srs), + UserWarning, + stacklevel=stacklevel, + ) + + # ----------------------------------------------------------------------------- # Constructors / converters to other formats # ----------------------------------------------------------------------------- -def from_shapely(data): +def _geom_to_shapely(geom): """ - Convert a list or array of shapely objects to a GeometryArray. + Convert internal representation (PyGEOS or Shapely) to external Shapely object. + """ + if not compat.USE_PYGEOS: + return geom + else: + return vectorized._pygeos_to_shapely(geom) - Validates the elements. + +def _shapely_to_geom(geom): """ - n = len(data) + Convert external Shapely object to internal representation (PyGEOS or Shapely). + """ + if not compat.USE_PYGEOS: + return geom + else: + return vectorized._shapely_to_pygeos(geom) - out = [] - for idx in range(n): - geom = data[idx] - if isinstance(geom, BaseGeometry): - out.append(geom) - elif hasattr(geom, "__geo_interface__"): - geom = shapely.geometry.asShape(geom) - out.append(geom) - elif _isna(geom): - out.append(None) - else: - raise TypeError("Input must be valid geometry objects: {0}".format(geom)) +def _is_scalar_geometry(geom): + if compat.USE_PYGEOS: + return isinstance(geom, pygeos.Geometry) + else: + return isinstance(geom, BaseGeometry) + + +def from_shapely(data, crs=None): + """ + Convert a list or array of shapely objects to a GeometryArray. + + Validates the elements. - aout = np.empty(n, dtype=object) - aout[:] = out - return GeometryArray(aout) + Parameters + ---------- + data : array-like + list or array of shapely objects + crs : value, optional + Coordinate Reference System of the geometry objects. Can be anything accepted by + :meth:`pyproj.CRS.from_user_input() `, + such as an authority string (eg "EPSG:4326") or a WKT string. + + """ + return GeometryArray(vectorized.from_shapely(data), crs=crs) def to_shapely(geoms): @@ -100,84 +172,72 @@ """ if not isinstance(geoms, GeometryArray): raise ValueError("'geoms' must be a GeometryArray") - return geoms.data + return vectorized.to_shapely(geoms.data) -def from_wkb(data): +def from_wkb(data, crs=None): """ Convert a list or array of WKB objects to a GeometryArray. - """ - import shapely.wkb - n = len(data) - - out = [] - - for idx in range(n): - geom = data[idx] - if geom is not None and len(geom): - geom = shapely.wkb.loads(geom) - else: - geom = None - out.append(geom) + Parameters + ---------- + data : array-like + list or array of WKB objects + crs : value, optional + Coordinate Reference System of the geometry objects. Can be anything accepted by + :meth:`pyproj.CRS.from_user_input() `, + such as an authority string (eg "EPSG:4326") or a WKT string. - aout = np.empty(n, dtype=object) - aout[:] = out - return GeometryArray(aout) + """ + return GeometryArray(vectorized.from_wkb(data), crs=crs) -def to_wkb(geoms): +def to_wkb(geoms, hex=False): """ Convert GeometryArray to a numpy object array of WKB objects. """ if not isinstance(geoms, GeometryArray): raise ValueError("'geoms' must be a GeometryArray") - out = [geom.wkb if geom is not None else None for geom in geoms] - return np.array(out, dtype=object) + return vectorized.to_wkb(geoms.data, hex=hex) -def from_wkt(data): +def from_wkt(data, crs=None): """ Convert a list or array of WKT objects to a GeometryArray. - """ - import shapely.wkt - - n = len(data) - out = [] - - for idx in range(n): - geom = data[idx] - if geom is not None and len(geom): - if isinstance(geom, bytes): - geom = geom.decode("utf-8") - geom = shapely.wkt.loads(geom) - else: - geom = None - out.append(geom) + Parameters + ---------- + data : array-like + list or array of WKT objects + crs : value, optional + Coordinate Reference System of the geometry objects. Can be anything accepted by + :meth:`pyproj.CRS.from_user_input() `, + such as an authority string (eg "EPSG:4326") or a WKT string. - aout = np.empty(n, dtype=object) - aout[:] = out - return GeometryArray(aout) + """ + return GeometryArray(vectorized.from_wkt(data), crs=crs) -def to_wkt(geoms): +def to_wkt(geoms, **kwargs): """ Convert GeometryArray to a numpy object array of WKT objects. """ if not isinstance(geoms, GeometryArray): raise ValueError("'geoms' must be a GeometryArray") - out = [geom.wkt if geom is not None else None for geom in geoms] - return np.array(out, dtype=object) + return vectorized.to_wkt(geoms.data, **kwargs) -def _points_from_xy(x, y, z=None): +def points_from_xy(x, y, z=None, crs=None): """ - Generate list of shapely Point geometries from x, y(, z) coordinates. + Generate GeometryArray of shapely Point geometries from x, y(, z) coordinates. Parameters ---------- - x, y, z : array + x, y, z : iterable + crs : value, optional + Coordinate Reference System of the geometry objects. Can be anything accepted by + :meth:`pyproj.CRS.from_user_input() `, + such as an authority string (eg "EPSG:4326") or a WKT string. Examples -------- @@ -188,229 +248,9 @@ Returns ------- - list : list - """ - if not len(x) == len(y): - raise ValueError("x and y arrays must be equal length.") - if z is not None: - if not len(z) == len(x): - raise ValueError("z array must be same length as x and y.") - geom = [shapely.geometry.Point(i, j, k) for i, j, k in zip(x, y, z)] - else: - geom = [shapely.geometry.Point(i, j) for i, j in zip(x, y)] - return geom - - -def points_from_xy(x, y, z=None): - """Convert arrays of x and y values to a GeometryArray of points.""" - x = np.asarray(x, dtype="float64") - y = np.asarray(y, dtype="float64") - if z is not None: - z = np.asarray(z, dtype="float64") - out = _points_from_xy(x, y, z) - aout = np.empty(len(x), dtype=object) - aout[:] = out - return GeometryArray(aout) - - -# ----------------------------------------------------------------------------- -# Helper methods for the vectorized operations -# ----------------------------------------------------------------------------- - - -def _binary_geo(op, left, right): - # type: (str, GeometryArray, [GeometryArray/BaseGeometry]) -> GeometryArray - """ Apply geometry-valued operation - - Supports: - - - difference - - symmetric_difference - - intersection - - union - - Parameters - ---------- - op: string - right: GeometryArray or single shapely BaseGeoemtry + output : GeometryArray """ - if isinstance(right, BaseGeometry): - # intersection can return empty GeometryCollections, and if the - # result are only those, numpy will coerce it to empty 2D array - data = np.empty(len(left), dtype=object) - data[:] = [ - getattr(s, op)(right) if s is not None and right is not None else None - for s in left.data - ] - return GeometryArray(data) - elif isinstance(right, GeometryArray): - if len(left) != len(right): - msg = "Lengths of inputs do not match. Left: {0}, Right: {1}".format( - len(left), len(right) - ) - raise ValueError(msg) - data = np.empty(len(left), dtype=object) - data[:] = [ - getattr(this_elem, op)(other_elem) - if this_elem is not None and other_elem is not None - else None - for this_elem, other_elem in zip(left.data, right.data) - ] - return GeometryArray(data) - else: - raise TypeError("Type not known: {0} vs {1}".format(type(left), type(right))) - - -def _binary_predicate(op, left, right, *args, **kwargs): - # type: (str, GeometryArray, GeometryArray/BaseGeometry, args/kwargs) - # -> array[bool] - """Binary operation on GeometryArray that returns a boolean ndarray - - Supports: - - - contains - - disjoint - - intersects - - touches - - crosses - - within - - overlaps - - covers - - covered_by - - equals - - Parameters - ---------- - op: string - right: GeometryArray or single shapely BaseGeoemtry - """ - # empty geometries are handled by shapely (all give False except disjoint) - if isinstance(right, BaseGeometry): - data = [ - getattr(s, op)(right, *args, **kwargs) if s is not None else False - for s in left.data - ] - return np.array(data, dtype=bool) - elif isinstance(right, GeometryArray): - if len(left) != len(right): - msg = "Lengths of inputs do not match. Left: {0}, Right: {1}".format( - len(left), len(right) - ) - raise ValueError(msg) - data = [ - getattr(this_elem, op)(other_elem, *args, **kwargs) - if not (this_elem is None or other_elem is None) - else False - for this_elem, other_elem in zip(left.data, right.data) - ] - return np.array(data, dtype=bool) - else: - raise TypeError("Type not known: {0} vs {1}".format(type(left), type(right))) - - -def _binary_op_float(op, left, right, *args, **kwargs): - # type: (str, GeometryArray, GeometryArray/BaseGeometry, args/kwargs) - # -> array - """Binary operation on GeometryArray that returns a ndarray""" - # used for distance -> check for empty as we want to return np.nan instead 0.0 - # as shapely does currently (https://github.com/Toblerity/Shapely/issues/498) - if isinstance(right, BaseGeometry): - data = [ - getattr(s, op)(right, *args, **kwargs) - if not (s is None or s.is_empty or right.is_empty) - else np.nan - for s in left.data - ] - return np.array(data, dtype=float) - elif isinstance(right, GeometryArray): - if len(left) != len(right): - msg = "Lengths of inputs do not match. Left: {0}, Right: {1}".format( - len(left), len(right) - ) - raise ValueError(msg) - data = [ - getattr(this_elem, op)(other_elem, *args, **kwargs) - if not (this_elem is None or this_elem.is_empty) - | (other_elem is None or other_elem.is_empty) - else np.nan - for this_elem, other_elem in zip(left.data, right.data) - ] - return np.array(data, dtype=float) - else: - raise TypeError("Type not known: {0} vs {1}".format(type(left), type(right))) - - -def _binary_op(op, left, right, *args, **kwargs): - # type: (str, GeometryArray, GeometryArray/BaseGeometry, args/kwargs) - # -> array - """Binary operation on GeometryArray that returns a ndarray""" - # pass empty to shapely (relate handles this correctly, project only - # for linestrings and points) - if op == "project": - null_value = np.nan - dtype = float - elif op == "relate": - null_value = None - dtype = object - else: - raise AssertionError("wrong op") - - if isinstance(right, BaseGeometry): - data = [ - getattr(s, op)(right, *args, **kwargs) if s is not None else null_value - for s in left.data - ] - return np.array(data, dtype=dtype) - elif isinstance(right, GeometryArray): - if len(left) != len(right): - msg = "Lengths of inputs do not match. Left: {0}, Right: {1}".format( - len(left), len(right) - ) - raise ValueError(msg) - data = [ - getattr(this_elem, op)(other_elem, *args, **kwargs) - if not (this_elem is None or other_elem is None) - else null_value - for this_elem, other_elem in zip(left.data, right.data) - ] - return np.array(data, dtype=dtype) - else: - raise TypeError("Type not known: {0} vs {1}".format(type(left), type(right))) - - -def _unary_geo(op, left, *args, **kwargs): - # type: (str, GeometryArray) -> GeometryArray - """Unary operation that returns new geometries""" - # ensure 1D output, see note above - data = np.empty(len(left), dtype=object) - data[:] = [getattr(geom, op, None) for geom in left.data] - return GeometryArray(data) - - -def _unary_op(op, left, null_value=False): - # type: (str, GeometryArray, Any) -> np.array - """Unary operation that returns a Series""" - data = [getattr(geom, op, null_value) for geom in left.data] - return np.array(data, dtype=np.dtype(type(null_value))) - - -def _affinity_method(op, left, *args, **kwargs): - # type: (str, GeometryArray, ...) -> GeometryArray - - # not all shapely.affinity methods can handle empty geometries: - # affine_transform itself works (as well as translate), but rotate, scale - # and skew fail (they try to unpack the bounds). - # Here: consistently returning empty geom for input empty geom - out = [] - for geom in left.data: - if geom is None or geom.is_empty: - res = geom - else: - res = getattr(shapely.affinity, op)(geom, *args, **kwargs) - out.append(res) - data = np.empty(len(left), dtype=object) - data[:] = out - return GeometryArray(data) + return GeometryArray(vectorized.points_from_xy(x, y, z), crs=crs) class GeometryArray(ExtensionArray): @@ -421,8 +261,10 @@ _dtype = GeometryDtype() - def __init__(self, data): + def __init__(self, data, crs=None): if isinstance(data, self.__class__): + if not crs: + crs = data.crs data = data.data elif not isinstance(data, np.ndarray): raise TypeError( @@ -435,6 +277,41 @@ ) self.data = data + self._crs = None + self.crs = crs + + @property + def crs(self): + """ + The Coordinate Reference System (CRS) represented as a ``pyproj.CRS`` + object. + + Returns None if the CRS is not set, and to set the value it + :getter: Returns a ``pyproj.CRS`` or None. When setting, the value + Coordinate Reference System of the geometry objects. Can be anything accepted by + :meth:`pyproj.CRS.from_user_input() `, + such as an authority string (eg "EPSG:4326") or a WKT string. + """ + return self._crs + + @crs.setter + def crs(self, value): + """Sets the value of the crs""" + self._crs = None if not value else CRS.from_user_input(value) + + def check_geographic_crs(self, stacklevel): + """Check CRS and warn if the planar operation is done in a geographic CRS""" + if self.crs and self.crs.is_geographic: + warnings.warn( + "Geometry is in a geographic CRS. Results from '{}' are likely " + "incorrect. Use 'GeoSeries.to_crs()' to re-project geometries to a " + "projected CRS before this operation.\n".format( + inspect.stack()[1].function + ), + UserWarning, + stacklevel=stacklevel, + ) + @property def dtype(self): return self._dtype @@ -444,19 +321,19 @@ def __getitem__(self, idx): if isinstance(idx, numbers.Integral): - return self.data[idx] + return _geom_to_shapely(self.data[idx]) # array-like, slice - if PANDAS_GE_10: + if compat.PANDAS_GE_10: # for pandas >= 1.0, validate and convert IntegerArray/BooleanArray # to numpy array, pass-through non-array-like indexers idx = pd.api.indexers.check_array_indexer(self, idx) if isinstance(idx, (Iterable, slice)): - return GeometryArray(self.data[idx]) + return GeometryArray(self.data[idx], crs=self.crs) else: raise TypeError("Index type not supported", idx) def __setitem__(self, key, value): - if PANDAS_GE_10: + if compat.PANDAS_GE_10: # for pandas >= 1.0, validate and convert IntegerArray/BooleanArray # keys to numpy array, pass-through non-array-like indexers key = pd.api.indexers.check_array_indexer(self, key) @@ -473,6 +350,10 @@ # internally only use None as missing value indicator # but accept others value = None + elif isinstance(value, BaseGeometry): + value = from_shapely([value]).data[0] + else: + raise TypeError("should be valid geometry") if isinstance(key, (slice, list, np.ndarray)): value_array = np.empty(1, dtype=object) value_array[:] = [value] @@ -484,54 +365,73 @@ "Value should be either a BaseGeometry or None, got %s" % str(value) ) + # TODO: use this once pandas-dev/pandas#33457 is fixed + # if hasattr(value, "crs"): + # if value.crs and (value.crs != self.crs): + # raise ValueError( + # "CRS mismatch between CRS of the passed geometries " + # "and CRS of existing geometries." + # ) + + if compat.USE_PYGEOS: + + def __getstate__(self): + return (pygeos.to_wkb(self.data), self._crs) + + def __setstate__(self, state): + geoms = pygeos.from_wkb(state[0]) + self._crs = state[1] + self.data = geoms + self.base = None + + else: + + def __setstate__(self, state): + if "_crs" not in state: + state["_crs"] = None + self.__dict__.update(state) + # ------------------------------------------------------------------------- # Geometry related methods # ------------------------------------------------------------------------- @property def is_valid(self): - return _unary_op("is_valid", self, null_value=False) + return vectorized.is_valid(self.data) @property def is_empty(self): - return _unary_op("is_empty", self, null_value=False) + return vectorized.is_empty(self.data) @property def is_simple(self): - return _unary_op("is_simple", self, null_value=False) + return vectorized.is_simple(self.data) @property def is_ring(self): - # operates on the exterior, so can't use _unary_op() - return np.array( - [ - geom.exterior.is_ring - if geom is not None and geom.exterior is not None - else False - for geom in self.data - ], - dtype=bool, - ) + return vectorized.is_ring(self.data) @property def is_closed(self): - return _unary_op("is_closed", self, null_value=False) + return vectorized.is_closed(self.data) @property def has_z(self): - return _unary_op("has_z", self, null_value=False) + return vectorized.has_z(self.data) @property def geom_type(self): - return _unary_op("geom_type", self, null_value=None) + return vectorized.geom_type(self.data) @property def area(self): - return _unary_op("area", self, null_value=np.nan) + self.check_geographic_crs(stacklevel=5) + return vectorized.area(self.data) @property def length(self): - return _unary_op("length", self, null_value=np.nan) + self.check_geographic_crs(stacklevel=5) + return vectorized.length(self.data) # # Unary operations that return new geometries @@ -539,195 +439,216 @@ @property def boundary(self): - return _unary_geo("boundary", self) + return GeometryArray(vectorized.boundary(self.data), crs=self.crs) @property def centroid(self): - return _unary_geo("centroid", self) + self.check_geographic_crs(stacklevel=5) + return GeometryArray(vectorized.centroid(self.data), crs=self.crs) @property def convex_hull(self): - return _unary_geo("convex_hull", self) + return GeometryArray(vectorized.convex_hull(self.data), crs=self.crs) @property def envelope(self): - return _unary_geo("envelope", self) + return GeometryArray(vectorized.envelope(self.data), crs=self.crs) @property def exterior(self): - return _unary_geo("exterior", self) + return GeometryArray(vectorized.exterior(self.data), crs=self.crs) @property def interiors(self): - has_non_poly = False - inner_rings = [] - for geom in self.data: - interior_ring_seq = getattr(geom, "interiors", None) - # polygon case - if interior_ring_seq is not None: - inner_rings.append(list(interior_ring_seq)) - # non-polygon case - else: - has_non_poly = True - inner_rings.append(None) - if has_non_poly: - warnings.warn( - "Only Polygon objects have interior rings. For other " - "geometry types, None is returned." - ) - data = np.empty(len(self), dtype=object) - data[:] = inner_rings - return data + # no GeometryArray as result + return vectorized.interiors(self.data) def representative_point(self): - # method and not a property -> can't use _unary_geo - data = np.empty(len(self), dtype=object) - data[:] = [ - geom.representative_point() if geom is not None else None - for geom in self.data - ] - return GeometryArray(data) + return GeometryArray(vectorized.representative_point(self.data), crs=self.crs) # # Binary predicates # + @staticmethod + def _binary_method(op, left, right, **kwargs): + if isinstance(right, GeometryArray): + if len(left) != len(right): + msg = "Lengths of inputs do not match. Left: {0}, Right: {1}".format( + len(left), len(right) + ) + raise ValueError(msg) + if not _check_crs(left, right): + _crs_mismatch_warn(left, right, stacklevel=7) + right = right.data + + return getattr(vectorized, op)(left.data, right, **kwargs) + def covers(self, other): - return _binary_predicate("covers", self, other) + return self._binary_method("covers", self, other) + + def covered_by(self, other): + return self._binary_method("covered_by", self, other) def contains(self, other): - return _binary_predicate("contains", self, other) + return self._binary_method("contains", self, other) def crosses(self, other): - return _binary_predicate("crosses", self, other) + return self._binary_method("crosses", self, other) def disjoint(self, other): - return _binary_predicate("disjoint", self, other) + return self._binary_method("disjoint", self, other) - def equals(self, other): - return _binary_predicate("equals", self, other) + def geom_equals(self, other): + return self._binary_method("equals", self, other) def intersects(self, other): - return _binary_predicate("intersects", self, other) + return self._binary_method("intersects", self, other) def overlaps(self, other): - return _binary_predicate("overlaps", self, other) + return self._binary_method("overlaps", self, other) def touches(self, other): - return _binary_predicate("touches", self, other) + return self._binary_method("touches", self, other) def within(self, other): - return _binary_predicate("within", self, other) + return self._binary_method("within", self, other) + + def geom_equals_exact(self, other, tolerance): + return self._binary_method("equals_exact", self, other, tolerance=tolerance) + + def geom_almost_equals(self, other, decimal): + return self.geom_equals_exact(other, 0.5 * 10 ** (-decimal)) + # return _binary_predicate("almost_equals", self, other, decimal=decimal) def equals_exact(self, other, tolerance): - return _binary_predicate("equals_exact", self, other, tolerance=tolerance) + warnings.warn( + "GeometryArray.equals_exact() is now GeometryArray.geom_equals_exact(). " + "GeometryArray.equals_exact() will be deprecated in the future.", + FutureWarning, + stacklevel=2, + ) + return self._binary_method("equals_exact", self, other, tolerance=tolerance) def almost_equals(self, other, decimal): - return _binary_predicate("almost_equals", self, other, decimal=decimal) + warnings.warn( + "GeometryArray.almost_equals() is now GeometryArray.geom_almost_equals(). " + "GeometryArray.almost_equals() will be deprecated in the future.", + FutureWarning, + stacklevel=2, + ) + return self.geom_equals_exact(other, 0.5 * 10 ** (-decimal)) # # Binary operations that return new geometries # def difference(self, other): - return _binary_geo("difference", self, other) + return GeometryArray( + self._binary_method("difference", self, other), crs=self.crs + ) def intersection(self, other): - return _binary_geo("intersection", self, other) + return GeometryArray( + self._binary_method("intersection", self, other), crs=self.crs + ) def symmetric_difference(self, other): - return _binary_geo("symmetric_difference", self, other) + return GeometryArray( + self._binary_method("symmetric_difference", self, other), crs=self.crs + ) def union(self, other): - return _binary_geo("union", self, other) + return GeometryArray(self._binary_method("union", self, other), crs=self.crs) # # Other operations # def distance(self, other): - return _binary_op_float("distance", self, other) + self.check_geographic_crs(stacklevel=6) + return self._binary_method("distance", self, other) def buffer(self, distance, resolution=16, **kwargs): - data = data = np.empty(len(self), dtype=object) - if isinstance(distance, np.ndarray): - if len(distance) != len(self): - raise ValueError( - "Length of distance sequence does not match " - "length of the GeoSeries" - ) - - data[:] = [ - geom.buffer(dist, resolution, **kwargs) if geom is not None else None - for geom, dist in zip(self.data, distance) - ] - return GeometryArray(data) - - data[:] = [ - geom.buffer(distance, resolution, **kwargs) if geom is not None else None - for geom in self.data - ] - return GeometryArray(data) + if not (isinstance(distance, (int, float)) and distance == 0): + self.check_geographic_crs(stacklevel=5) + return GeometryArray( + vectorized.buffer(self.data, distance, resolution=resolution, **kwargs), + crs=self.crs, + ) def interpolate(self, distance, normalized=False): - data = data = np.empty(len(self), dtype=object) - if isinstance(distance, np.ndarray): - if len(distance) != len(self): - raise ValueError( - "Length of distance sequence does not match " - "length of the GeoSeries" - ) - data[:] = [ - geom.interpolate(dist, normalized=normalized) - for geom, dist in zip(self.data, distance) - ] - return GeometryArray(data) - - data[:] = [ - geom.interpolate(distance, normalized=normalized) for geom in self.data - ] - return GeometryArray(data) - - def simplify(self, *args, **kwargs): - # method and not a property -> can't use _unary_geo - data = np.empty(len(self), dtype=object) - data[:] = [geom.simplify(*args, **kwargs) for geom in self.data] - return GeometryArray(data) + self.check_geographic_crs(stacklevel=5) + return GeometryArray( + vectorized.interpolate(self.data, distance, normalized=normalized), + crs=self.crs, + ) + + def simplify(self, tolerance, preserve_topology=True): + return GeometryArray( + vectorized.simplify( + self.data, tolerance, preserve_topology=preserve_topology + ), + crs=self.crs, + ) def project(self, other, normalized=False): - return _binary_op("project", self, other, normalized=normalized) + if isinstance(other, BaseGeometry): + other = _shapely_to_geom(other) + elif isinstance(other, GeometryArray): + other = other.data + return vectorized.project(self.data, other, normalized=normalized) def relate(self, other): - return _binary_op("relate", self, other) + if isinstance(other, GeometryArray): + other = other.data + return vectorized.relate(self.data, other) # # Reduction operations that return a Shapely geometry # def unary_union(self): - return shapely.ops.unary_union(self.data) + return vectorized.unary_union(self.data) # # Affinity operations # def affine_transform(self, matrix): - return _affinity_method("affine_transform", self, matrix) + return GeometryArray( + vectorized._affinity_method("affine_transform", self.data, matrix), + crs=self.crs, + ) def translate(self, xoff=0.0, yoff=0.0, zoff=0.0): - return _affinity_method("translate", self, xoff, yoff, zoff) + return GeometryArray( + vectorized._affinity_method("translate", self.data, xoff, yoff, zoff), + crs=self.crs, + ) def rotate(self, angle, origin="center", use_radians=False): - return _affinity_method( - "rotate", self, angle, origin=origin, use_radians=use_radians + return GeometryArray( + vectorized._affinity_method( + "rotate", self.data, angle, origin=origin, use_radians=use_radians + ), + crs=self.crs, ) def scale(self, xfact=1.0, yfact=1.0, zfact=1.0, origin="center"): - return _affinity_method("scale", self, xfact, yfact, zfact, origin=origin) + return GeometryArray( + vectorized._affinity_method( + "scale", self.data, xfact, yfact, zfact, origin=origin + ), + crs=self.crs, + ) def skew(self, xs=0.0, ys=0.0, origin="center", use_radians=False): - return _affinity_method( - "skew", self, xs, ys, origin=origin, use_radians=use_radians + return GeometryArray( + vectorized._affinity_method( + "skew", self.data, xs, ys, origin=origin, use_radians=use_radians + ), + crs=self.crs, ) # @@ -738,7 +659,7 @@ def x(self): """Return the x location of point geometries in a GeoSeries""" if (self.geom_type[~self.isna()] == "Point").all(): - return _unary_op("x", self, null_value=np.nan) + return vectorized.get_x(self.data) else: message = "x attribute access only provided for Point geometries" raise ValueError(message) @@ -747,27 +668,14 @@ def y(self): """Return the y location of point geometries in a GeoSeries""" if (self.geom_type[~self.isna()] == "Point").all(): - return _unary_op("y", self, null_value=np.nan) + return vectorized.get_y(self.data) else: message = "y attribute access only provided for Point geometries" raise ValueError(message) @property def bounds(self): - # ensure that for empty arrays, the result has the correct shape - if len(self) == 0: - return np.empty((0, 4), dtype="float64") - # need to explicitly check for empty (in addition to missing) geometries, - # as those return an empty tuple, not resulting in a 2D array - bounds = np.array( - [ - geom.bounds - if not (geom is None or geom.is_empty) - else (np.nan, np.nan, np.nan, np.nan) - for geom in self.data - ] - ) - return bounds + return vectorized.bounds(self.data) @property def total_bounds(self): @@ -778,12 +686,12 @@ b = self.bounds return np.array( ( - b[:, 0].min(), # minx - b[:, 1].min(), # miny - b[:, 2].max(), # maxx - b[:, 3].max(), + np.nanmin(b[:, 0]), # minx + np.nanmin(b[:, 1]), # miny + np.nanmax(b[:, 2]), # maxx + np.nanmax(b[:, 3]), # maxy ) - ) # maxy + ) # ------------------------------------------------------------------------- # general array like compat @@ -803,26 +711,30 @@ def copy(self, *args, **kwargs): # still taking args/kwargs for compat with pandas 0.24 - return GeometryArray(self.data.copy()) + return GeometryArray(self.data.copy(), crs=self._crs) def take(self, indices, allow_fill=False, fill_value=None): from pandas.api.extensions import take if allow_fill: if fill_value is None or pd.isna(fill_value): - fill_value = 0 + fill_value = None + elif isinstance(fill_value, BaseGeometry): + fill_value = _shapely_to_geom(fill_value) + elif not _is_scalar_geometry(fill_value): + raise TypeError("provide geometry or None as fill value") result = take(self.data, indices, allow_fill=allow_fill, fill_value=fill_value) - if fill_value == 0: - result[result == 0] = None - return GeometryArray(result) + if allow_fill and fill_value is None: + result[pd.isna(result)] = None + return GeometryArray(result, crs=self.crs) def _fill(self, idx, value): """ Fill index locations with value Value should be a BaseGeometry """ - if not (isinstance(value, BaseGeometry) or value is None): + if not (_is_scalar_geometry(value) or value is None): raise TypeError( "Value should be either a BaseGeometry or None, got %s" % str(value) ) @@ -869,6 +781,7 @@ raise NotImplementedError( "fillna currently only supports filling with a scalar geometry" ) + value = _shapely_to_geom(value) new_values = new_values._fill(mask, value) return new_values @@ -899,7 +812,13 @@ elif pd.api.types.is_string_dtype(dtype) and not pd.api.types.is_object_dtype( dtype ): - return to_wkt(self).astype(dtype, copy=False) + string_values = to_wkt(self) + if compat.PANDAS_GE_10: + pd_dtype = pd.api.types.pandas_dtype(dtype) + if isinstance(pd_dtype, pd.StringDtype): + # ensure to return a pandas string array instead of numpy array + return pd.array(string_values, dtype="string") + return string_values.astype(dtype, copy=False) else: return np.array(self, dtype=dtype, copy=copy) @@ -907,7 +826,10 @@ """ Boolean NumPy array indicating if each value is missing """ - return np.array([g is None for g in self.data], dtype="bool") + if compat.USE_PYGEOS: + return pygeos.is_missing(self.data) + else: + return np.array([g is None for g in self.data], dtype="bool") def unique(self): """Compute the ExtensionArray of unique values. @@ -949,6 +871,9 @@ ------- ExtensionArray """ + # GH 1413 + if isinstance(scalars, BaseGeometry): + scalars = [scalars] return from_shapely(scalars) def _values_for_factorize(self): @@ -1066,13 +991,14 @@ ExtensionArray """ data = np.concatenate([ga.data for ga in to_concat]) - return GeometryArray(data) + return GeometryArray(data, crs=to_concat[0].crs) def _reduce(self, name, skipna=True, **kwargs): # including the base class version here (that raises by default) # because this was not yet defined in pandas 0.23 if name == "any" or name == "all": - return getattr(self.data, name)() + # TODO(pygeos) + return getattr(to_shapely(self), name)() raise TypeError( "cannot perform {name} with type {dtype}".format( name=name, dtype=self.dtype @@ -1087,7 +1013,7 @@ ------- values : numpy array """ - return self.data + return to_shapely(self) def _binop(self, other, op): def convert_values(param): @@ -1111,7 +1037,7 @@ # a TypeError should be raised res = [op(a, b) for (a, b) in zip(lvalues, rvalues)] - res = np.asarray(res) + res = np.asarray(res, dtype=bool) return res def __eq__(self, other): diff -Nru python-geopandas-0.7.0/geopandas/base.py python-geopandas-0.8.1/geopandas/base.py --- python-geopandas-0.7.0/geopandas/base.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/base.py 2020-07-15 17:54:36.000000000 +0000 @@ -4,7 +4,6 @@ import pandas as pd from pandas import DataFrame, MultiIndex, Series -from pyproj import CRS from shapely.geometry import box from shapely.geometry.base import BaseGeometry from shapely.ops import cascaded_union @@ -12,17 +11,11 @@ import geopandas as gpd from .array import GeometryArray, GeometryDtype +from .sindex import get_sindex_class, has_sindex -try: - from rtree.core import RTreeError - - HAS_SINDEX = True -except ImportError: - - class RTreeError(Exception): - pass - - HAS_SINDEX = False +# for backwards compat +# this will be static (will NOT follow USE_PYGEOS changes) +HAS_SINDEX = has_sindex() def is_geometry_type(data): @@ -42,10 +35,12 @@ # type: (str, GeoSeries, GeoSeries) -> GeoSeries/Series this = this.geometry if isinstance(other, GeoPandasBase): - this, other = this.align(other.geometry) + if not this.index.equals(other.index): + warn("The indices of the two GeoSeries are different.") + this, other = this.align(other.geometry) + else: + other = other.geometry - if this.crs != other.crs: - warn("GeoSeries crs mismatch: {0} and {1}".format(this.crs, other.crs)) a_this = GeometryArray(this.values) other = GeometryArray(other.values) elif isinstance(other, BaseGeometry): @@ -100,23 +95,22 @@ _sindex_generated = False def _generate_sindex(self): - if not HAS_SINDEX: - warn("Cannot generate spatial index: Missing package `rtree`.") - else: - from geopandas.sindex import SpatialIndex - - stream = ( - (i, item.bounds, idx) - for i, (idx, item) in enumerate(self.geometry.iteritems()) - if pd.notnull(item) and not item.is_empty - ) - try: - self._sindex = SpatialIndex(stream) - # What we really want here is an empty generator error, or - # for the bulk loader to log that the generator was empty - # and move on. See https://github.com/Toblerity/rtree/issues/20. - except RTreeError: - pass + sindex_cls = get_sindex_class() + if sindex_cls is not None: + _sindex = sindex_cls(self.geometry) + if not _sindex.is_empty: + self._sindex = _sindex + else: + warn( + "Generated spatial index is empty and returned `None`. " + "Future versions of GeoPandas will return zero-length spatial " + "index instead of `None`. Use `len(gdf.sindex) > 0` " + "or `if gdf.sindex` instead of `if gd.sindex is not None` " + "to check for empty spatial indexes.", + FutureWarning, + stacklevel=3, + ) + self._sindex = None self._sindex_generated = True def _invalidate_sindex(self): @@ -142,15 +136,16 @@ Returns None if the CRS is not set, and to set the value it :getter: Returns a ``pyproj.CRS`` or None. When setting, the value - can be anything accepted by :meth:`pyproj.CRS.from_user_input`, + can be anything accepted by + :meth:`pyproj.CRS.from_user_input() `, such as an authority string (eg "EPSG:4326") or a WKT string. """ - return self._crs + return self.geometry.values.crs @crs.setter def crs(self, value): """Sets the value of the crs""" - self._crs = None if not value else CRS.from_user_input(value) + self.geometry.values.crs = value @property def geom_type(self): @@ -176,8 +171,33 @@ @property def is_empty(self): - """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for - empty geometries.""" + """ + Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for + empty geometries. + + Examples + -------- + An example of a GeoDataFrame with one empty point, one point and one missing + value: + + >>> from shapely.geometry import Point + >>> d = {'geometry': [Point(), Point(2,1), None]} + >>> gdf = gpd.GeoDataFrame(d, crs="EPSG:4326") + >>> gdf + geometry + 0 GEOMETRYCOLLECTION EMPTY + 1 POINT (2.00000 1.00000) + 2 None + >>> gdf.is_empty + 0 True + 1 False + 2 False + dtype: bool + + See Also + -------- + GeoSeries.isna : detect missing values + """ return _delegate_property("is_empty", self) @property @@ -320,14 +340,14 @@ The GeoSeries (elementwise) or geometric object to test for equality. """ - return _binary_op("equals", self, other) + return _binary_op("geom_equals", self, other) def geom_almost_equals(self, other, decimal=6): """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` if each geometry is approximately equal to `other`. Approximate equality is tested at all points to the specified `decimal` - place precision. See also :meth:`equals`. + place precision. See also :meth:`geom_equals`. Parameters ---------- @@ -336,12 +356,12 @@ decimal : int Decimal place presion used when testing for approximate equality. """ - return _binary_op("almost_equals", self, other, decimal=decimal) + return _binary_op("geom_almost_equals", self, other, decimal=decimal) def geom_equals_exact(self, other, tolerance): """Return True for all geometries that equal *other* to a given tolerance, else False""" - return _binary_op("equals_exact", self, other, tolerance=tolerance) + return _binary_op("geom_equals_exact", self, other, tolerance=tolerance) def crosses(self, other): """Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for @@ -390,7 +410,14 @@ return _binary_op("intersects", self, other) def overlaps(self, other): - """Return True for all geometries that overlap *other*, else False""" + """Returns True for all geometries that overlap *other*, else False. + + Parameters + ---------- + other : GeoSeries or geometric object + The GeoSeries (elementwise) or geometric object to test if + overlaps. + """ return _binary_op("overlaps", self, other) def touches(self, other): @@ -430,6 +457,44 @@ """ return _binary_op("within", self, other) + def covers(self, other): + """ + Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for + each geometry that is entirely covering `other`. + + An object A is said to cover another object B if no points of B lie + in the exterior of A. + + See + https://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html + for reference. + + Parameters + ---------- + other : Geoseries or geometric object + The Geoseries (elementwise) or geometric object to check is being covered. + """ + return _binary_geo("covers", self, other) + + def covered_by(self, other): + """ + Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for + each geometry that is entirely covered by `other`. + + An object A is said to cover another object B if no points of B lie + in the exterior of A. + + See + https://lin-ear-th-inking.blogspot.com/2007/06/subtleties-of-ogc-covers-spatial.html + for reference. + + Parameters + ---------- + other : Geoseries or geometric object + The Geoseries (elementwise) or geometric object to check is being covered. + """ + return _binary_geo("covered_by", self, other) + def distance(self, other): """Returns a ``Series`` containing the distance to `other`. @@ -746,8 +811,8 @@ original index and a zero-based integer index that counts the number of single geometries within a multi-part geometry. - Example - ------- + Examples + -------- >>> gdf # gdf is GeoSeries of MultiPoints 0 MULTIPOINT (0 0, 1 1) 1 MULTIPOINT (2 2, 3 3, 4 4) @@ -788,6 +853,34 @@ """ return _CoordinateIndexer(self) + def equals(self, other): + """ + Test whether two objects contain the same elements. + + This function allows two GeoSeries or GeoDataFrames to be compared + against each other to see if they have the same shape and elements. + Missing values in the same location are considered equal. The + row/column index do not need to have the same type (as long as the + values are still considered equal), but the dtypes of the respective + columns must be the same. + + Parameters + ---------- + other : GeoSeries or GeoDataFrame + The other GeoSeries or GeoDataFrame to be compared with the first. + + Returns + ------- + bool + True if all elements are the same in both objects, False + otherwise. + """ + # we override this because pandas is using `self._constructor` in the + # isinstance check (https://github.com/geopandas/geopandas/issues/1420) + if not isinstance(other, type(self)): + return False + return self._data.equals(other._data) + class _CoordinateIndexer(object): # see docstring GeoPandasBase.cx property above diff -Nru python-geopandas-0.7.0/geopandas/_compat.py python-geopandas-0.8.1/geopandas/_compat.py --- python-geopandas-0.7.0/geopandas/_compat.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/_compat.py 2020-07-15 17:54:36.000000000 +0000 @@ -1,6 +1,10 @@ from distutils.version import LooseVersion +import importlib +import os +import warnings import pandas as pd +import shapely # ----------------------------------------------------------------------------- # pandas compat @@ -9,3 +13,141 @@ PANDAS_GE_024 = str(pd.__version__) >= LooseVersion("0.24.0") PANDAS_GE_025 = str(pd.__version__) >= LooseVersion("0.25.0") PANDAS_GE_10 = str(pd.__version__) >= LooseVersion("0.26.0.dev") +PANDAS_GE_11 = str(pd.__version__) >= LooseVersion("1.1.0.dev") + + +# ----------------------------------------------------------------------------- +# Shapely / PyGEOS compat +# ----------------------------------------------------------------------------- + + +SHAPELY_GE_17 = str(shapely.__version__) >= LooseVersion("1.7.0") + +HAS_PYGEOS = None +USE_PYGEOS = None +PYGEOS_SHAPELY_COMPAT = None + +try: + import pygeos # noqa + + HAS_PYGEOS = True +except ImportError: + HAS_PYGEOS = False + + +def set_use_pygeos(val=None): + """ + Set the global configuration on whether to use PyGEOS or not. + + The default is use PyGEOS if it is installed. This can be overridden + with an environment variable USE_PYGEOS (this is only checked at + first import, cannot be changed during interactive session). + + Alternatively, pass a value here to force a True/False value. + """ + global USE_PYGEOS + global PYGEOS_SHAPELY_COMPAT + + if val is not None: + USE_PYGEOS = bool(val) + else: + if USE_PYGEOS is None: + + USE_PYGEOS = HAS_PYGEOS + + env_use_pygeos = os.getenv("USE_PYGEOS", None) + if env_use_pygeos is not None: + USE_PYGEOS = bool(int(env_use_pygeos)) + + # validate the pygeos version + if USE_PYGEOS: + try: + import pygeos # noqa + + # validate the pygeos version + if not str(pygeos.__version__) >= LooseVersion("0.6"): + raise ImportError( + "PyGEOS >= 0.6 is required, version {0} is installed".format( + pygeos.__version__ + ) + ) + + # Check whether Shapely and PyGEOS use the same GEOS version. + # Based on PyGEOS from_shapely implementation. + + from shapely.geos import geos_version_string as shapely_geos_version + from pygeos import geos_capi_version_string + + # shapely has something like: "3.6.2-CAPI-1.10.2 4d2925d6" + # pygeos has something like: "3.6.2-CAPI-1.10.2" + if not shapely_geos_version.startswith(geos_capi_version_string): + warnings.warn( + "The Shapely GEOS version ({}) is incompatible with the GEOS " + "version PyGEOS was compiled with ({}). Conversions between both " + "will be slow.".format( + shapely_geos_version, geos_capi_version_string + ) + ) + PYGEOS_SHAPELY_COMPAT = False + else: + PYGEOS_SHAPELY_COMPAT = True + + except ImportError: + raise ImportError( + "To use the PyGEOS speed-ups within GeoPandas, you need to install " + "PyGEOS: 'conda install pygeos' or 'pip install pygeos'" + ) + + +set_use_pygeos() + + +def import_optional_dependency(name: str, extra: str = ""): + """ + Import an optional dependency. + + Adapted from pandas.compat._optional::import_optional_dependency + + Raises a formatted ImportError if the module is not present. + + Parameters + ---------- + name : str + The module name. + extra : str + Additional text to include in the ImportError message. + Returns + ------- + module + """ + msg = """Missing optional dependency '{name}'. {extra} " + "Use pip or conda to install {name}.""".format( + name=name, extra=extra + ) + + if not isinstance(name, str): + raise ValueError( + "Invalid module name: '{name}'; must be a string".format(name=name) + ) + + try: + module = importlib.import_module(name) + + except ImportError: + raise ImportError(msg) from None + + return module + + +# ----------------------------------------------------------------------------- +# RTree compat +# ----------------------------------------------------------------------------- + +HAS_RTREE = None +RTREE_GE_094 = False +try: + import rtree # noqa + + HAS_RTREE = True +except ImportError: + HAS_RTREE = False diff -Nru python-geopandas-0.7.0/geopandas/_config.py python-geopandas-0.8.1/geopandas/_config.py --- python-geopandas-0.7.0/geopandas/_config.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/_config.py 2020-07-15 17:54:36.000000000 +0000 @@ -31,6 +31,8 @@ if option.validator: option.validator(value) self._config[key] = value + if option.callback: + option.callback(key, value) else: msg = "You can only set the value of existing options" raise AttributeError(msg) @@ -58,7 +60,7 @@ else: doc_text = u"No description available." doc_text = indent(doc_text, prefix=" ") - description += doc_text + description += doc_text + "\n" space = "\n " description = description.replace("\n", space) return "{}({}{})".format(cls, space, description) @@ -100,4 +102,36 @@ callback=None, ) -options = Options({"display_precision": display_precision}) + +def _validate_bool(value): + if not isinstance(value, bool): + raise TypeError("Expected bool value, got {0}".format(type(value))) + + +def _default_use_pygeos(): + import geopandas._compat as compat + + return compat.USE_PYGEOS + + +def _callback_use_pygeos(key, value): + assert key == "use_pygeos" + import geopandas._compat as compat + + compat.set_use_pygeos(value) + + +use_pygeos = Option( + key="use_pygeos", + default_value=_default_use_pygeos(), + doc=( + "Whether to use PyGEOS to speed up spatial operations. The default is True " + "if PyGEOS is installed, and follows the USE_PYGEOS environment variable " + "if set." + ), + validator=_validate_bool, + callback=_callback_use_pygeos, +) + + +options = Options({"display_precision": display_precision, "use_pygeos": use_pygeos}) diff -Nru python-geopandas-0.7.0/geopandas/geodataframe.py python-geopandas-0.8.1/geopandas/geodataframe.py --- python-geopandas-0.7.0/geopandas/geodataframe.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/geodataframe.py 2020-07-15 17:54:36.000000000 +0000 @@ -1,26 +1,34 @@ import json +import warnings import numpy as np import pandas as pd from pandas import DataFrame, Series from shapely.geometry import mapping, shape +from shapely.geometry.base import BaseGeometry -from geopandas.array import GeometryArray, from_shapely + +from pyproj import CRS + +from geopandas.array import GeometryArray, from_shapely, GeometryDtype from geopandas.base import GeoPandasBase, is_geometry_type from geopandas.geoseries import GeoSeries import geopandas.io from geopandas.plotting import plot_dataframe + DEFAULT_GEO_COLUMN_NAME = "geometry" -def _ensure_geometry(data): +def _ensure_geometry(data, crs=None): """ Ensure the data is of geometry dtype or converted to it. If input is a (Geo)Series, output is a GeoSeries, otherwise output is GeometryArray. + + If the input is a GeometryDtype with a set CRS, `crs` is ignored. """ if is_geometry_type(data): if isinstance(data, Series): @@ -28,10 +36,10 @@ return data else: if isinstance(data, Series): - out = from_shapely(np.asarray(data)) + out = from_shapely(np.asarray(data), crs=crs) return GeoSeries(out, index=data.index, name=data.name) else: - out = from_shapely(data) + out = from_shapely(data, crs=crs) return out @@ -43,11 +51,32 @@ Parameters ---------- - crs : str (optional) - Coordinate system + crs : value (optional) + Coordinate Reference System of the geometry objects. Can be anything accepted by + :meth:`pyproj.CRS.from_user_input() `, + such as an authority string (eg "EPSG:4326") or a WKT string. geometry : str or array (optional) If str, column to use as geometry. If array, will be set as 'geometry' column on GeoDataFrame. + + Examples + -------- + Constructing GeoDataFrame from a dictionary. + + >>> from shapely.geometry import Point + >>> d = {'col1': ['name1', 'name2'], 'geometry': [Point(1,2), Point(2,1)]} + >>> gdf = gpd.GeoDataFrame(d, crs="EPSG:4326") + >>> gdf + col1 geometry + 0 name1 POINT (1.00000 2.00000) + 1 name2 POINT (2.00000 1.00000) + + Notice that the inferred dtype of 'geometry' columns is geometry. + + >>> gdf.dtypes + col1 object + geometry geometry + dtype: object """ _metadata = ["_crs", "_geometry_column_name"] @@ -61,8 +90,7 @@ # need to set this before calling self['geometry'], because # getitem accesses crs - self._crs = None - self.crs = crs + self._crs = crs if crs is not None else None # set_geometry ensures the geometry data have the proper dtype, # but is not called if `geometry=None` ('geometry' column present @@ -74,7 +102,24 @@ # only if we have actual geometry values -> call set_geometry index = self.index try: - self["geometry"] = _ensure_geometry(self["geometry"].values) + if ( + hasattr(self["geometry"].values, "crs") + and self["geometry"].values.crs + and crs + and not self["geometry"].values.crs == crs + ): + warnings.warn( + "CRS mismatch between CRS of the passed geometries " + "and 'crs'. Use 'GeoDataFrame.set_crs(crs, " + "allow_override=True)' to overwrite CRS or " + "'GeoDataFrame.to_crs(crs)' to reproject geometries. " + "CRS mismatch will raise an error in the future versions " + "of GeoPandas.", + FutureWarning, + stacklevel=2, + ) + # TODO: raise error in 0.9 or 0.10. + self["geometry"] = _ensure_geometry(self["geometry"].values, crs) except TypeError: pass else: @@ -86,9 +131,34 @@ geometry = "geometry" if geometry is not None: + if ( + hasattr(geometry, "crs") + and geometry.crs + and crs + and not geometry.crs == crs + ): + warnings.warn( + "CRS mismatch between CRS of the passed geometries " + "and 'crs'. Use 'GeoDataFrame.set_crs(crs, " + "allow_override=True)' to overwrite CRS or " + "'GeoDataFrame.to_crs(crs)' to reproject geometries. " + "CRS mismatch will raise an error in the future versions " + "of GeoPandas.", + FutureWarning, + stacklevel=2, + ) + # TODO: raise error in 0.9 or 0.10. self.set_geometry(geometry, inplace=True) self._invalidate_sindex() + if geometry is None and crs: + warnings.warn( + "Assigning CRS to a GeoDataFrame without a geometry column is now " + "deprecated and will not be supported in the future.", + FutureWarning, + stacklevel=2, + ) + def __setattr__(self, attr, val): # have to special case geometry b/c pandas tries to use as column... if attr == "geometry": @@ -100,7 +170,7 @@ if self._geometry_column_name not in self: raise AttributeError( "No geometry data set yet (expected in" - " column '%s'." % self._geometry_column_name + " column '%s'.)" % self._geometry_column_name ) return self[self._geometry_column_name] @@ -127,10 +197,12 @@ Delete column to be used as the new geometry inplace : boolean, default False Modify the GeoDataFrame in place (do not create a new object) - crs : str/result of fion.get_crs (optional) - Coordinate system to use. If passed, overrides both DataFrame and - col's crs. Otherwise, tries to get crs from passed col values or - DataFrame. + crs : pyproj.CRS, optional + Coordinate system to use. The value can be anything accepted + by :meth:`pyproj.CRS.from_user_input() `, + such as an authority string (eg "EPSG:4326") or a WKT string. + If passed, overrides both DataFrame and col's crs. + Otherwise, tries to get crs from passed col values or DataFrame. Examples -------- @@ -139,7 +211,7 @@ Returns ------- - geodataframe : GeoDataFrame + GeoDataFrame """ # Most of the code here is taken from DataFrame.set_index() if inplace: @@ -147,9 +219,6 @@ else: frame = self.copy() - if not crs: - crs = getattr(col, "crs", self.crs) - to_remove = None geo_column_name = self._geometry_column_name if isinstance(col, (Series, list, np.ndarray, GeometryArray)): @@ -172,13 +241,17 @@ if to_remove: del frame[to_remove] - if isinstance(level, GeoSeries) and level.crs != crs: + if not crs: + level_crs = getattr(level, "crs", None) + crs = level_crs if level_crs is not None else self._crs + + if isinstance(level, (GeoSeries, GeometryArray)) and level.crs != crs: # Avoids caching issues/crs sharing issues level = level.copy() level.crs = crs # Check that we are using a listlike of geometries - level = _ensure_geometry(level) + level = _ensure_geometry(level, crs=crs) index = frame.index frame[geo_column_name] = level if frame.index is not index and len(frame.index) == len(index): @@ -223,6 +296,61 @@ self.rename(columns={geometry_col: col}, inplace=inplace) self.set_geometry(col, inplace=inplace) + @property + def crs(self): + """ + The Coordinate Reference System (CRS) represented as a ``pyproj.CRS`` + object. + + Returns None if the CRS is not set, and to set the value it + :getter: Returns a ``pyproj.CRS`` or None. When setting, the value + can be anything accepted by + :meth:`pyproj.CRS.from_user_input() `, + such as an authority string (eg "EPSG:4326") or a WKT string. + """ + return self._crs + + @crs.setter + def crs(self, value): + """Sets the value of the crs""" + if self._geometry_column_name not in self: + warnings.warn( + "Assigning CRS to a GeoDataFrame without a geometry column is now " + "deprecated and will not be supported in the future.", + FutureWarning, + stacklevel=4, + ) + self._crs = None if not value else CRS.from_user_input(value) + else: + if hasattr(self.geometry.values, "crs"): + self.geometry.values.crs = value + self._crs = self.geometry.values.crs + else: + # column called 'geometry' without geometry + self._crs = None if not value else CRS.from_user_input(value) + + def __setstate__(self, state): + # overriding DataFrame method for compat with older pickles (CRS handling) + if isinstance(state, dict): + if "_metadata" in state and "crs" in state["_metadata"]: + metadata = state["_metadata"] + metadata[metadata.index("crs")] = "_crs" + if "crs" in state and "_crs" not in state: + crs = state.pop("crs") + state["_crs"] = CRS.from_user_input(crs) if crs is not None else crs + + super().__setstate__(state) + + # for some versions that didn't yet have CRS at array level -> crs is set + # at GeoDataFrame level with '_crs' (and not 'crs'), so without propagating + # to the GeoSeries/GeometryArray + try: + if self.crs is not None: + if self.geometry.values.crs is None: + self.crs = self.crs + except Exception: + pass + @classmethod def from_file(cls, filename, **kwargs): """Alternate constructor to create a ``GeoDataFrame`` from a file. @@ -245,7 +373,7 @@ -------- >>> df = geopandas.GeoDataFrame.from_file('nybb.shp') """ - return geopandas.io.file.read_file(filename, **kwargs) + return geopandas.io.file._read_file(filename, **kwargs) @classmethod def from_features(cls, features, crs=None, columns=None): @@ -291,18 +419,17 @@ features_lst = features rows = [] - for f in features_lst: - if hasattr(f, "__geo_interface__"): - f = f.__geo_interface__ - else: - f = f - - d = {"geometry": shape(f["geometry"]) if f["geometry"] else None} - d.update(f["properties"]) - rows.append(d) - df = GeoDataFrame(rows, columns=columns) - df.crs = crs - return df + for feature in features_lst: + # load geometry + if hasattr(feature, "__geo_interface__"): + feature = feature.__geo_interface__ + row = { + "geometry": shape(feature["geometry"]) if feature["geometry"] else None + } + # load properties + row.update(feature["properties"]) + rows.append(row) + return GeoDataFrame(rows, columns=columns, crs=crs) @classmethod def from_postgis( @@ -315,6 +442,7 @@ coerce_float=True, parse_dates=None, params=None, + chunksize=None, ): """ Alternate constructor to create a ``GeoDataFrame`` from a sql query @@ -344,6 +472,9 @@ without native Datetime support, such as SQLite. params : list, tuple or dict, optional, default None List of parameters to pass to execute method. + chunksize : int, default None + If specified, return an iterator where chunksize is the number + of rows to include in each chunk. Examples -------- @@ -353,7 +484,7 @@ >>> df = geopandas.GeoDataFrame.from_postgis(sql, con) """ - df = geopandas.io.sql.read_postgis( + df = geopandas.io.sql._read_postgis( sql, con, geom_col=geom_col, @@ -362,6 +493,7 @@ coerce_float=coerce_float, parse_dates=parse_dates, params=params, + chunksize=chunksize, ) return df @@ -424,6 +556,12 @@ if na not in ["null", "drop", "keep"]: raise ValueError("Unknown na method {0}".format(na)) + if self._geometry_column_name not in self: + raise AttributeError( + "No geometry data set (expected in" + " column '%s')." % self._geometry_column_name + ) + ids = np.array(self.index, copy=False) geometries = np.array(self[self._geometry_column_name], copy=False) @@ -484,6 +622,81 @@ return geo + def to_parquet(self, path, index=None, compression="snappy", **kwargs): + """Write a GeoDataFrame to the Parquet format. + + Any geometry columns present are serialized to WKB format in the file. + + Requires 'pyarrow'. + + WARNING: this is an initial implementation of Parquet file support and + associated metadata. This is tracking version 0.1.0 of the metadata + specification at: + https://github.com/geopandas/geo-arrow-spec + + This metadata specification does not yet make stability promises. As such, + we do not yet recommend using this in a production setting unless you are + able to rewrite your Parquet files. + + .. versionadded:: 0.8 + + Parameters + ---------- + path : str, path object + index : bool, default None + If ``True``, always include the dataframe's index(es) as columns + in the file output. + If ``False``, the index(es) will not be written to the file. + If ``None``, the index(ex) will be included as columns in the file + output except `RangeIndex` which is stored as metadata only. + compression : {'snappy', 'gzip', 'brotli', None}, default 'snappy' + Name of the compression to use. Use ``None`` for no compression. + kwargs + Additional keyword arguments passed to to pyarrow.parquet.write_table(). + """ + + from geopandas.io.arrow import _to_parquet + + _to_parquet(self, path, compression=compression, index=index, **kwargs) + + def to_feather(self, path, index=None, compression=None, **kwargs): + """Write a GeoDataFrame to the Feather format. + + Any geometry columns present are serialized to WKB format in the file. + + Requires 'pyarrow' >= 0.17. + + WARNING: this is an initial implementation of Feather file support and + associated metadata. This is tracking version 0.1.0 of the metadata + specification at: + https://github.com/geopandas/geo-arrow-spec + + This metadata specification does not yet make stability promises. As such, + we do not yet recommend using this in a production setting unless you are + able to rewrite your Feather files. + + .. versionadded:: 0.8 + + Parameters + ---------- + path : str, path object + index : bool, default None + If ``True``, always include the dataframe's index(es) as columns + in the file output. + If ``False``, the index(es) will not be written to the file. + If ``None``, the index(ex) will be included as columns in the file + output except `RangeIndex` which is stored as metadata only. + compression : {'zstd', 'lz4', 'uncompressed'}, optional + Name of the compression to use. Use ``"uncompressed"`` for no + compression. By default uses LZ4 if available, otherwise uncompressed. + kwargs + Additional keyword arguments passed to to pyarrow.feather.write_feather(). + """ + + from geopandas.io.arrow import _to_feather + + _to_feather(self, path, index=index, compression=compression, **kwargs) + def to_file( self, filename, driver="ESRI Shapefile", schema=None, index=None, **kwargs ): @@ -528,34 +741,73 @@ -------- GeoSeries.to_file """ - from geopandas.io.file import to_file + from geopandas.io.file import _to_file - to_file(self, filename, driver, schema, index, **kwargs) + _to_file(self, filename, driver, schema, index, **kwargs) + + def set_crs(self, crs=None, epsg=None, inplace=False, allow_override=False): + """ + Set the Coordinate Reference System (CRS) of the ``GeoDataFrame``. + + If there are multiple geometry columns within the GeoDataFrame, only + the CRS of the active geometry column is set. + + NOTE: The underlying geometries are not transformed to this CRS. To + transform the geometries to a new CRS, use the ``to_crs`` method. + + Parameters + ---------- + crs : pyproj.CRS, optional if `epsg` is specified + The value can be anything accepted + by :meth:`pyproj.CRS.from_user_input() `, + such as an authority string (eg "EPSG:4326") or a WKT string. + epsg : int, optional if `crs` is specified + EPSG code specifying the projection. + inplace : bool, default False + If True, the CRS of the GeoDataFrame will be changed in place + (while still returning the result) instead of making a copy of + the GeoDataFrame. + allow_override : bool, default False + If the the GeoDataFrame already has a CRS, allow to replace the + existing CRS, even when both are not equal. + """ + if not inplace: + df = self.copy() + else: + df = self + df.geometry = df.geometry.set_crs( + crs=crs, epsg=epsg, allow_override=allow_override, inplace=True + ) + return df def to_crs(self, crs=None, epsg=None, inplace=False): """Transform geometries to a new coordinate reference system. - Transform all geometries in a GeoSeries to a different coordinate + Transform all geometries in an active geometry column to a different coordinate reference system. The ``crs`` attribute on the current GeoSeries must be set. Either ``crs`` or ``epsg`` may be specified for output. - This method will transform all points in all objects. It has no notion + This method will transform all points in all objects. It has no notion or projecting entire geometries. All segments joining points are - assumed to be lines in the current projection, not geodesics. Objects + assumed to be lines in the current projection, not geodesics. Objects crossing the dateline (or other projection boundary) will have undesirable behavior. Parameters ---------- crs : pyproj.CRS, optional if `epsg` is specified - The value can be anything accepted - by :meth:`pyproj.CRS.from_user_input`, such as an authority - string (eg "EPSG:4326") or a WKT string. + The value can be anything accepted by + :meth:`pyproj.CRS.from_user_input() `, + such as an authority string (eg "EPSG:4326") or a WKT string. epsg : int, optional if `crs` is specified EPSG code specifying output projection. inplace : bool, optional, default: False Whether to return a new GeoDataFrame or do the transformation in place. + + Returns + ------- + GeoDataFrame """ if inplace: df = self @@ -575,19 +827,31 @@ """ result = super(GeoDataFrame, self).__getitem__(key) geo_col = self._geometry_column_name - if isinstance(key, str) and key == geo_col: + if isinstance(result, Series) and isinstance(result.dtype, GeometryDtype): result.__class__ = GeoSeries - result.crs = self.crs result._invalidate_sindex() elif isinstance(result, DataFrame) and geo_col in result: result.__class__ = GeoDataFrame - result.crs = self.crs result._geometry_column_name = geo_col result._invalidate_sindex() elif isinstance(result, DataFrame) and geo_col not in result: result.__class__ = DataFrame return result + def __setitem__(self, key, value): + """ + Overwritten to preserve CRS of GeometryArray in cases like + df['geometry'] = [geom... for geom in df.geometry] + """ + if not pd.api.types.is_list_like(key) and key == self._geometry_column_name: + if pd.api.types.is_scalar(value) or isinstance(value, BaseGeometry): + value = [value] * self.shape[0] + try: + value = _ensure_geometry(value, crs=self.crs) + except TypeError: + warnings.warn("Geometry column does not contain geometry.") + super(GeoDataFrame, self).__setitem__(key, value) + # # Implement pandas methods # @@ -641,6 +905,7 @@ else: for name in self._metadata: object.__setattr__(self, name, getattr(other, name, None)) + return self def plot(self, *args, **kwargs): @@ -728,6 +993,9 @@ """ df_copy = self.copy() + if "level_1" in df_copy.columns: # GH1393 + df_copy = df_copy.rename(columns={"level_1": "__level_1"}) + exploded_geom = df_copy.geometry.explode().reset_index(level=-1) exploded_index = exploded_geom.columns[0] @@ -738,6 +1006,10 @@ # exploded GeoSeries index. df.set_index(exploded_index, append=True, inplace=True) df.index.names = list(self.index.names) + [None] + + if "__level_1" in df.columns: + df = df.rename(columns={"__level_1": "level_1"}) + geo_df = df.set_geometry(self._geometry_column_name) return geo_df @@ -767,6 +1039,106 @@ # do not return a GeoDataFrame return pd.DataFrame(df) + def to_postgis( + self, + name, + con, + schema=None, + if_exists="fail", + index=False, + index_label=None, + chunksize=None, + dtype=None, + ): + + """ + Upload GeoDataFrame into PostGIS database. + + This method requires SQLAlchemy and GeoAlchemy2, and a PostgreSQL + Python driver (e.g. psycopg2) to be installed. + + Parameters + ---------- + name : str + Name of the target table. + con : sqlalchemy.engine.Engine + Active connection to the PostGIS database. + if_exists : {'fail', 'replace', 'append'}, default 'fail' + How to behave if the table already exists: + + - fail: Raise a ValueError. + - replace: Drop the table before inserting new values. + - append: Insert new values to the existing table. + schema : string, optional + Specify the schema. If None, use default schema: 'public'. + index : bool, default True + Write DataFrame index as a column. + Uses *index_label* as the column name in the table. + index_label : string or sequence, default None + Column label for index column(s). + If None is given (default) and index is True, + then the index names are used. + chunksize : int, optional + Rows will be written in batches of this size at a time. + By default, all rows will be written at once. + dtype : dict of column name to SQL type, default None + Specifying the datatype for columns. + The keys should be the column names and the values + should be the SQLAlchemy types. + + Examples + -------- + + >>> from sqlalchemy import create_engine + >>> engine = create_engine("postgres://myusername:mypassword@myhost:5432\ +/mydatabase";) + >>> gdf.to_postgis("my_table", engine) + """ + geopandas.io.sql._write_postgis( + self, name, con, schema, if_exists, index, index_label, chunksize, dtype + ) + + # + # Implement standard operators for GeoSeries + # + + def __xor__(self, other): + """Implement ^ operator as for builtin set type""" + warnings.warn( + "'^' operator will be deprecated. Use the 'symmetric_difference' " + "method instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.geometry.symmetric_difference(other) + + def __or__(self, other): + """Implement | operator as for builtin set type""" + warnings.warn( + "'|' operator will be deprecated. Use the 'union' method instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.geometry.union(other) + + def __and__(self, other): + """Implement & operator as for builtin set type""" + warnings.warn( + "'&' operator will be deprecated. Use the 'intersection' method instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.geometry.intersection(other) + + def __sub__(self, other): + """Implement - operator as for builtin set type""" + warnings.warn( + "'-' operator will be deprecated. Use the 'difference' method instead.", + DeprecationWarning, + stacklevel=2, + ) + return self.geometry.difference(other) + def _dataframe_set_geometry(self, col, drop=False, inplace=False, crs=None): if inplace: diff -Nru python-geopandas-0.7.0/geopandas/geoseries.py python-geopandas-0.8.1/geopandas/geoseries.py --- python-geopandas-0.7.0/geopandas/geoseries.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/geoseries.py 2020-07-15 17:54:36.000000000 +0000 @@ -8,13 +8,13 @@ from pyproj import CRS, Transformer from shapely.geometry.base import BaseGeometry -from shapely.ops import transform from geopandas.base import GeoPandasBase, _delegate_property from geopandas.plotting import plot_series -from .array import GeometryDtype, from_shapely +from .array import GeometryArray, GeometryDtype, from_shapely from .base import is_geometry_type +from . import _vectorized as vectorized _SERIES_WARNING_MSG = """\ @@ -42,6 +42,24 @@ return Series(data=data, index=index, **kwargs) +def inherit_doc(cls): + """ + A decorator adding a docstring from an existing method. + """ + + def decorator(decorated): + original_method = getattr(cls, decorated.__name__, None) + if original_method: + doc = original_method.__doc__ or "" + else: + doc = "" + + decorated.__doc__ = doc + return decorated + + return decorator + + class GeoSeries(GeoPandasBase, Series): """ A Series object designed to store shapely geometry objects. @@ -52,8 +70,11 @@ The geometries to store in the GeoSeries. index : array-like or Index The index for the GeoSeries. - crs : str, dict (optional) - Coordinate Reference System of the geometry objects. + crs : value (optional) + Coordinate Reference System of the geometry objects. Can be anything accepted by + :meth:`pyproj.CRS.from_user_input() `, + such as an authority string (eg "EPSG:4326") or a WKT string. + kwargs Additional arguments passed to the Series constructor, e.g. ``name``. @@ -76,11 +97,30 @@ """ - _metadata = ["name", "_crs"] + _metadata = ["name"] def __new__(cls, data=None, index=None, crs=None, **kwargs): # we need to use __new__ because we want to return Series instance # instead of GeoSeries instance in case of non-geometry data + + if hasattr(data, "crs") and crs: + if not data.crs: + # make a copy to avoid setting CRS to passed GeometryArray + data = data.copy() + else: + if not data.crs == crs: + warnings.warn( + "CRS mismatch between CRS of the passed geometries " + "and 'crs'. Use 'GeoDataFrame.set_crs(crs, " + "allow_override=True)' to overwrite CRS or " + "'GeoSeries.to_crs(crs)' to reproject geometries. " + "CRS mismatch will raise an error in the future versions " + "of GeoPandas.", + FutureWarning, + stacklevel=2, + ) + # TODO: raise error in 0.9 or 0.10. + if isinstance(data, SingleBlockManager): if isinstance(data.blocks[0].dtype, GeometryDtype): if data.blocks[0].ndim == 2: @@ -96,7 +136,7 @@ data = SingleBlockManager([block], data.axes[0], fastpath=True) self = super(GeoSeries, cls).__new__(cls) super(GeoSeries, self).__init__(data, index=index, **kwargs) - self.crs = crs + self.crs = getattr(self.values, "crs", crs) return self warnings.warn(_SERIES_WARNING_MSG, FutureWarning, stacklevel=2) return Series(data, index=index, **kwargs) @@ -125,7 +165,7 @@ return s # try to convert to GeometryArray, if fails return plain Series try: - data = from_shapely(s.values) + data = from_shapely(s.values, crs) except TypeError: warnings.warn(_SERIES_WARNING_MSG, FutureWarning, stacklevel=2) return s @@ -134,7 +174,9 @@ self = super(GeoSeries, cls).__new__(cls) super(GeoSeries, self).__init__(data, index=index, name=name, **kwargs) - self.crs = crs + + if not self.crs: + self.crs = crs self._invalidate_sindex() return self @@ -260,15 +302,26 @@ def __getitem__(self, key): return self._wrapped_pandas_method("__getitem__", key) + @inherit_doc(pd.Series) def sort_index(self, *args, **kwargs): return self._wrapped_pandas_method("sort_index", *args, **kwargs) + @inherit_doc(pd.Series) def take(self, *args, **kwargs): return self._wrapped_pandas_method("take", *args, **kwargs) + @inherit_doc(pd.Series) def select(self, *args, **kwargs): return self._wrapped_pandas_method("select", *args, **kwargs) + @inherit_doc(pd.Series) + def apply(self, func, args=(), **kwargs): + result = super().apply(func, args=args, **kwargs) + if isinstance(result, GeoSeries): + if self.crs is not None: + result.set_crs(self.crs, inplace=True) + return result + def __finalize__(self, other, method=None, **kwargs): """ propagate metadata from other to self """ # NOTE: backported from pandas master (upcoming v0.13) @@ -394,6 +447,54 @@ # Additional methods # + def set_crs(self, crs=None, epsg=None, inplace=False, allow_override=False): + """ + Set the Coordinate Reference System (CRS) of a ``GeoSeries``. + + NOTE: The underlying geometries are not transformed to this CRS. To + transform the geometries to a new CRS, use the ``to_crs`` method. + + Parameters + ---------- + crs : pyproj.CRS, optional if `epsg` is specified + The value can be anything accepted + by :meth:`pyproj.CRS.from_user_input() `, + such as an authority string (eg "EPSG:4326") or a WKT string. + epsg : int, optional if `crs` is specified + EPSG code specifying the projection. + inplace : bool, default False + If True, the CRS of the GeoSeries will be changed in place + (while still returning the result) instead of making a copy of + the GeoSeries. + allow_override : bool, default False + If the the GeoSeries already has a CRS, allow to replace the + existing CRS, even when both are not equal. + + Returns + ------- + GeoSeries + """ + if crs is not None: + crs = CRS.from_user_input(crs) + elif epsg is not None: + crs = CRS.from_epsg(epsg) + else: + raise ValueError("Must pass either crs or epsg.") + + if not allow_override and self.crs is not None and not self.crs == crs: + raise ValueError( + "The GeoSeries already has a CRS which is not equal to the passed " + "CRS. Specify 'allow_override=True' to allow replacing the existing " + "CRS without doing any transformation. If you actually want to " + "transform the geometries, use 'GeoSeries.to_crs' instead." + ) + if not inplace: + result = self.copy() + else: + result = self + result.crs = crs + return result + def to_crs(self, crs=None, epsg=None): """Returns a ``GeoSeries`` with all geometries transformed to a new coordinate reference system. @@ -412,10 +513,14 @@ ---------- crs : pyproj.CRS, optional if `epsg` is specified The value can be anything accepted - by :meth:`pyproj.CRS.from_user_input`, such as an authority - string (eg "EPSG:4326") or a WKT string. + by :meth:`pyproj.CRS.from_user_input() `, + such as an authority string (eg "EPSG:4326") or a WKT string. epsg : int, optional if `crs` is specified EPSG code specifying output projection. + + Returns + ------- + GeoSeries """ if self.crs is None: raise ValueError( @@ -434,11 +539,11 @@ return self transformer = Transformer.from_crs(self.crs, crs, always_xy=True) - result = self.apply(lambda geom: transform(transformer.transform, geom)) - result.__class__ = GeoSeries - result.crs = crs - result._invalidate_sindex() - return result + + new_data = vectorized.transform(self.values.data, transformer.transform) + return GeoSeries( + GeometryArray(new_data), crs=crs, index=self.index, name=self.name + ) def to_json(self, **kwargs): """ @@ -456,16 +561,37 @@ def __xor__(self, other): """Implement ^ operator as for builtin set type""" + warnings.warn( + "'^' operator will be deprecated. Use the 'symmetric_difference' " + "method instead.", + DeprecationWarning, + stacklevel=2, + ) return self.symmetric_difference(other) def __or__(self, other): """Implement | operator as for builtin set type""" + warnings.warn( + "'|' operator will be deprecated. Use the 'union' method instead.", + DeprecationWarning, + stacklevel=2, + ) return self.union(other) def __and__(self, other): """Implement & operator as for builtin set type""" + warnings.warn( + "'&' operator will be deprecated. Use the 'intersection' method instead.", + DeprecationWarning, + stacklevel=2, + ) return self.intersection(other) def __sub__(self, other): """Implement - operator as for builtin set type""" + warnings.warn( + "'-' operator will be deprecated. Use the 'difference' method instead.", + DeprecationWarning, + stacklevel=2, + ) return self.difference(other) diff -Nru python-geopandas-0.7.0/geopandas/__init__.py python-geopandas-0.8.1/geopandas/__init__.py --- python-geopandas-0.7.0/geopandas/__init__.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/__init__.py 2020-07-15 17:54:36.000000000 +0000 @@ -1,9 +1,13 @@ +from geopandas._config import options # noqa + from geopandas.geoseries import GeoSeries # noqa from geopandas.geodataframe import GeoDataFrame # noqa -from geopandas.array import _points_from_xy as points_from_xy # noqa +from geopandas.array import points_from_xy # noqa -from geopandas.io.file import read_file # noqa -from geopandas.io.sql import read_postgis # noqa +from geopandas.io.file import _read_file as read_file # noqa +from geopandas.io.arrow import _read_parquet as read_parquet # noqa +from geopandas.io.arrow import _read_feather as read_feather # noqa +from geopandas.io.sql import _read_postgis as read_postgis # noqa from geopandas.tools import sjoin # noqa from geopandas.tools import overlay # noqa from geopandas.tools._show_versions import show_versions # noqa @@ -12,7 +16,6 @@ import geopandas.datasets # noqa -from geopandas._config import options # noqa # make the interactive namespace easier to use # for `from geopandas import *` demos. diff -Nru python-geopandas-0.7.0/geopandas/io/arrow.py python-geopandas-0.8.1/geopandas/io/arrow.py --- python-geopandas-0.7.0/geopandas/io/arrow.py 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/io/arrow.py 2020-07-15 17:54:36.000000000 +0000 @@ -0,0 +1,454 @@ +from distutils.version import LooseVersion +import json +import warnings + +from pandas import DataFrame + +from geopandas._compat import import_optional_dependency +from geopandas.array import from_wkb, to_wkb +from geopandas import GeoDataFrame +import geopandas + + +METADATA_VERSION = "0.1.0" +# reference: https://github.com/geopandas/geo-arrow-spec + +# Metadata structure: +# { +# "geo": { +# "columns": { +# "": { +# "crs": "", +# "encoding": "WKB" +# } +# }, +# "creator": { +# "library": "geopandas", +# "version": "" +# } +# "primary_column": "", +# "schema_version": "" +# } +# } + + +def _create_metadata(df): + """Create and encode geo metadata dict. + + Parameters + ---------- + df : GeoDataFrame + + Returns + ------- + dict + """ + + # Construct metadata for each geometry + column_metadata = {} + for col in df.columns[df.dtypes == "geometry"]: + series = df[col] + column_metadata[col] = { + "crs": series.crs.to_wkt() if series.crs else None, + "encoding": "WKB", + "bbox": series.total_bounds.tolist(), + } + + return { + "primary_column": df._geometry_column_name, + "columns": column_metadata, + "schema_version": METADATA_VERSION, + "creator": {"library": "geopandas", "version": geopandas.__version__}, + } + + +def _encode_metadata(metadata): + """Encode metadata dict to UTF-8 JSON string + + Parameters + ---------- + metadata : dict + + Returns + ------- + UTF-8 encoded JSON string + """ + return json.dumps(metadata).encode("utf-8") + + +def _encode_wkb(df): + """Encode all geometry columns in the GeoDataFrame to WKB. + + Parameters + ---------- + df : GeoDataFrame + + Returns + ------- + DataFrame + geometry columns are encoded to WKB + """ + + df = DataFrame(df.copy()) + + # Encode all geometry columns to WKB + for col in df.columns[df.dtypes == "geometry"]: + df[col] = to_wkb(df[col].values) + + return df + + +def _decode_metadata(metadata_str): + """Decode a UTF-8 encoded JSON string to dict + + Parameters + ---------- + metadata_str : string (UTF-8 encoded) + + Returns + ------- + dict + """ + if metadata_str is None: + return None + + return json.loads(metadata_str.decode("utf-8")) + + +def _validate_dataframe(df): + """Validate that the GeoDataFrame conforms to requirements for writing + to Parquet format. + + Raises `ValueError` if the GeoDataFrame is not valid. + + copied from `pandas.io.parquet` + + Parameters + ---------- + df : GeoDataFrame + """ + + if not isinstance(df, DataFrame): + raise ValueError("Writing to Parquet/Feather only supports IO with DataFrames") + + # must have value column names (strings only) + if df.columns.inferred_type not in {"string", "unicode", "empty"}: + raise ValueError("Writing to Parquet/Feather requires string column names") + + # index level names must be strings + valid_names = all( + isinstance(name, str) for name in df.index.names if name is not None + ) + if not valid_names: + raise ValueError("Index level names must be strings") + + +def _validate_metadata(metadata): + """Validate geo metadata. + Must not be empty, and must contain the structure specified above. + + Raises ValueError if metadata is not valid. + + Parameters + ---------- + metadata : dict + """ + + if not metadata: + raise ValueError("Missing or malformed geo metadata in Parquet/Feather file") + + required_keys = ("primary_column", "columns") + for key in required_keys: + if metadata.get(key, None) is None: + raise ValueError( + "'geo' metadata in Parquet/Feather file is missing required key: " + "'{key}'".format(key=key) + ) + + if not isinstance(metadata["columns"], dict): + raise ValueError("'columns' in 'geo' metadata must be a dict") + + # Validate that geometry columns have required metadata and values + required_col_keys = ("crs", "encoding") + for col, column_metadata in metadata["columns"].items(): + for key in required_col_keys: + if key not in column_metadata: + raise ValueError( + "'geo' metadata in Parquet/Feather file is missing required key " + "'{key}' for column '{col}'".format(key=key, col=col) + ) + + if column_metadata["encoding"] != "WKB": + raise ValueError("Only WKB geometry encoding is supported") + + +def _geopandas_to_arrow(df, index=None): + """ + Helper function with main, shared logic for to_parquet/to_feather. + """ + from pyarrow import Table + + warnings.warn( + "this is an initial implementation of Parquet/Feather file support and " + "associated metadata. This is tracking version 0.1.0 of the metadata " + "specification at " + "https://github.com/geopandas/geo-arrow-spec\n\n" + "This metadata specification does not yet make stability promises. " + "We do not yet recommend using this in a production setting unless you " + "are able to rewrite your Parquet/Feather files.\n\n" + "To further ignore this warning, you can do: \n" + "import warnings; warnings.filterwarnings('ignore', " + "message='.*initial implementation of Parquet.*')", + UserWarning, + stacklevel=4, + ) + + _validate_dataframe(df) + + # create geo metadata before altering incoming data frame + geo_metadata = _create_metadata(df) + + df = _encode_wkb(df) + + table = Table.from_pandas(df, preserve_index=index) + + # Store geopandas specific file-level metadata + # This must be done AFTER creating the table or it is not persisted + metadata = table.schema.metadata + metadata.update({b"geo": _encode_metadata(geo_metadata)}) + return table.replace_schema_metadata(metadata) + + +def _to_parquet(df, path, index=None, compression="snappy", **kwargs): + """ + Write a GeoDataFrame to the Parquet format. + + Any geometry columns present are serialized to WKB format in the file. + + Requires 'pyarrow'. + + WARNING: this is an initial implementation of Parquet file support and + associated metadata. This is tracking version 0.1.0 of the metadata + specification at: + https://github.com/geopandas/geo-arrow-spec + + This metadata specification does not yet make stability promises. As such, + we do not yet recommend using this in a production setting unless you are + able to rewrite your Parquet files. + + + .. versionadded:: 0.8 + + Parameters + ---------- + path : str, path object + index : bool, default None + If ``True``, always include the dataframe's index(es) as columns + in the file output. + If ``False``, the index(es) will not be written to the file. + If ``None``, the index(ex) will be included as columns in the file + output except `RangeIndex` which is stored as metadata only. + compression : {'snappy', 'gzip', 'brotli', None}, default 'snappy' + Name of the compression to use. Use ``None`` for no compression. + kwargs + Additional keyword arguments passed to pyarrow.parquet.write_table(). + """ + parquet = import_optional_dependency( + "pyarrow.parquet", extra="pyarrow is required for Parquet support." + ) + + table = _geopandas_to_arrow(df, index=index) + parquet.write_table(table, path, compression=compression, **kwargs) + + +def _to_feather(df, path, index=None, compression=None, **kwargs): + """ + Write a GeoDataFrame to the Feather format. + + Any geometry columns present are serialized to WKB format in the file. + + Requires 'pyarrow' >= 0.17. + + WARNING: this is an initial implementation of Feather file support and + associated metadata. This is tracking version 0.1.0 of the metadata + specification at: + https://github.com/geopandas/geo-arrow-spec + + This metadata specification does not yet make stability promises. As such, + we do not yet recommend using this in a production setting unless you are + able to rewrite your Feather files. + + .. versionadded:: 0.8 + + Parameters + ---------- + path : str, path object + index : bool, default None + If ``True``, always include the dataframe's index(es) as columns + in the file output. + If ``False``, the index(es) will not be written to the file. + If ``None``, the index(ex) will be included as columns in the file + output except `RangeIndex` which is stored as metadata only. + compression : {'zstd', 'lz4', 'uncompressed'}, optional + Name of the compression to use. Use ``"uncompressed"`` for no + compression. By default uses LZ4 if available, otherwise uncompressed. + kwargs + Additional keyword arguments passed to pyarrow.feather.write_feather(). + """ + feather = import_optional_dependency( + "pyarrow.feather", extra="pyarrow is required for Feather support." + ) + # TODO move this into `import_optional_dependency` + import pyarrow + + if pyarrow.__version__ < LooseVersion("0.17.0"): + raise ImportError("pyarrow >= 0.17 required for Feather support") + + table = _geopandas_to_arrow(df, index=index) + feather.write_feather(table, path, compression=compression, **kwargs) + + +def _arrow_to_geopandas(table): + """ + Helper function with main, shared logic for read_parquet/read_feather. + """ + df = table.to_pandas() + + metadata = table.schema.metadata + if b"geo" not in metadata: + raise ValueError( + """Missing geo metadata in Parquet/Feather file. + Use pandas.read_parquet/read_feather() instead.""" + ) + + try: + metadata = _decode_metadata(metadata.get(b"geo", b"")) + + except (TypeError, json.decoder.JSONDecodeError): + raise ValueError("Missing or malformed geo metadata in Parquet/Feather file") + + _validate_metadata(metadata) + + # Find all geometry columns that were read from the file. May + # be a subset if 'columns' parameter is used. + geometry_columns = df.columns.intersection(metadata["columns"]) + + if not len(geometry_columns): + raise ValueError( + """No geometry columns are included in the columns read from + the Parquet/Feather file. To read this file without geometry columns, + use pandas.read_parquet/read_feather() instead.""" + ) + + geometry = metadata["primary_column"] + + # Missing geometry likely indicates a subset of columns was read; + # promote the first available geometry to the primary geometry. + if len(geometry_columns) and geometry not in geometry_columns: + geometry = geometry_columns[0] + + # if there are multiple non-primary geometry columns, raise a warning + if len(geometry_columns) > 1: + warnings.warn( + "Multiple non-primary geometry columns read from Parquet/Feather " + "file. The first column read was promoted to the primary geometry." + ) + + # Convert the WKB columns that are present back to geometry. + for col in geometry_columns: + df[col] = from_wkb(df[col].values, crs=metadata["columns"][col]["crs"]) + + return GeoDataFrame(df, geometry=geometry) + + +def _read_parquet(path, columns=None, **kwargs): + """ + Load a Parquet object from the file path, returning a GeoDataFrame. + + You can read a subset of columns in the file using the ``columns`` parameter. + However, the structure of the returned GeoDataFrame will depend on which + columns you read: + + * if no geometry columns are read, this will raise a ``ValueError`` - you + should use the pandas `read_parquet` method instead. + * if the primary geometry column saved to this file is not included in + columns, the first available geometry column will be set as the geometry + column of the returned GeoDataFrame. + + Requires 'pyarrow'. + + .. versionadded:: 0.8 + + Parameters + ---------- + path : str, path object + columns : list-like of strings, default=None + If not None, only these columns will be read from the file. If + the primary geometry column is not included, the first secondary + geometry read from the file will be set as the geometry column + of the returned GeoDataFrame. If no geometry columns are present, + a ``ValueError`` will be raised. + **kwargs + Any additional kwargs passed to pyarrow.parquet.read_table(). + + Returns + ------- + GeoDataFrame + """ + + parquet = import_optional_dependency( + "pyarrow.parquet", extra="pyarrow is required for Parquet support." + ) + + kwargs["use_pandas_metadata"] = True + table = parquet.read_table(path, columns=columns, **kwargs) + + return _arrow_to_geopandas(table) + + +def _read_feather(path, columns=None, **kwargs): + """ + Load a Feather object from the file path, returning a GeoDataFrame. + + You can read a subset of columns in the file using the ``columns`` parameter. + However, the structure of the returned GeoDataFrame will depend on which + columns you read: + + * if no geometry columns are read, this will raise a ``ValueError`` - you + should use the pandas `read_feather` method instead. + * if the primary geometry column saved to this file is not included in + columns, the first available geometry column will be set as the geometry + column of the returned GeoDataFrame. + + Requires 'pyarrow' >= 0.17. + + .. versionadded:: 0.8 + + Parameters + ---------- + path : str, path object + columns : list-like of strings, default=None + If not None, only these columns will be read from the file. If + the primary geometry column is not included, the first secondary + geometry read from the file will be set as the geometry column + of the returned GeoDataFrame. If no geometry columns are present, + a ``ValueError`` will be raised. + **kwargs + Any additional kwargs passed to pyarrow.feather.read_table(). + + Returns + ------- + GeoDataFrame + """ + + feather = import_optional_dependency( + "pyarrow.feather", extra="pyarrow is required for Feather support." + ) + # TODO move this into `import_optional_dependency` + import pyarrow + + if pyarrow.__version__ < LooseVersion("0.17.0"): + raise ImportError("pyarrow >= 0.17 required for Feather support") + + table = feather.read_table(path, columns=columns, **kwargs) + return _arrow_to_geopandas(table) diff -Nru python-geopandas-0.7.0/geopandas/io/file.py python-geopandas-0.8.1/geopandas/io/file.py --- python-geopandas-0.7.0/geopandas/io/file.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/io/file.py 2020-07-15 17:54:36.000000000 +0000 @@ -1,9 +1,11 @@ from distutils.version import LooseVersion +import io import numpy as np import pandas as pd import fiona +import pyproj from shapely.geometry import mapping from shapely.geometry.base import BaseGeometry @@ -34,7 +36,7 @@ return False -def read_file(filename, bbox=None, mask=None, rows=None, **kwargs): +def _read_file(filename, bbox=None, mask=None, rows=None, **kwargs): """ Returns a GeoDataFrame from a file or URL. @@ -42,22 +44,23 @@ Parameters ---------- - filename: str + filename : str, path object or file-like object Either the absolute or relative path to the file or URL to - be opened. - bbox: tuple | GeoDataFrame or GeoSeries | shapely Geometry, default None + be opened, or any object with a read() method (such as an open file + or StringIO) + bbox : tuple | GeoDataFrame or GeoSeries | shapely Geometry, default None Filter features by given bounding box, GeoSeries, GeoDataFrame or a shapely geometry. CRS mis-matches are resolved if given a GeoSeries or GeoDataFrame. Cannot be used with mask. - mask: dict | GeoDataFrame or GeoSeries | shapely Geometry, default None + mask : dict | GeoDataFrame or GeoSeries | shapely Geometry, default None Filter for features that intersect with the given dict-like geojson geometry, GeoSeries, GeoDataFrame or shapely geometry. CRS mis-matches are resolved if given a GeoSeries or GeoDataFrame. Cannot be used with bbox. - rows: int or slice, default None + rows : int or slice, default None Load in specific rows by passing an integer (first `n` rows) or a slice() object. - **kwargs: + **kwargs : Keyword args to be passed to the `open` or `BytesCollection` method in the fiona library when opening the file. For more information on possible keywords, type: @@ -69,7 +72,8 @@ Returns ------- - :obj:`geopandas.GeoDataFrame` + :obj:`geopandas.GeoDataFrame` or :obj:`pandas.DataFrame` : + If `ignore_geometry=True` a :obj:`pandas.DataFrame` will be returned. Notes ----- @@ -81,6 +85,9 @@ req = _urlopen(filename) path_or_bytes = req.read() reader = fiona.BytesCollection + elif isinstance(filename, io.TextIOBase): + path_or_bytes = filename.read() + reader = fiona.open else: path_or_bytes = filename reader = fiona.open @@ -122,14 +129,55 @@ f_filt = features.filter(bbox=bbox, mask=mask) else: f_filt = features + # get list of columns + columns = list(features.schema["properties"]) + if kwargs.get("ignore_geometry", False): + return pd.DataFrame( + [record["properties"] for record in f_filt], columns=columns + ) - columns = list(features.meta["schema"]["properties"]) + ["geometry"] - gdf = GeoDataFrame.from_features(f_filt, crs=crs, columns=columns) + return GeoDataFrame.from_features( + f_filt, crs=crs, columns=columns + ["geometry"] + ) - return gdf +def read_file(*args, **kwargs): + import warnings -def to_file(df, filename, driver="ESRI Shapefile", schema=None, index=None, **kwargs): + warnings.warn( + "geopandas.io.file.read_file() is intended for internal " + "use only, and will be deprecated. Use geopandas.read_file() instead.", + DeprecationWarning, + stacklevel=2, + ) + + return _read_file(*args, **kwargs) + + +def to_file(*args, **kwargs): + import warnings + + warnings.warn( + "geopandas.io.file.to_file() is intended for internal " + "use only, and will be deprecated. Use GeoDataFrame.to_file() " + "or GeoSeries.to_file() instead.", + DeprecationWarning, + stacklevel=2, + ) + + return _to_file(*args, **kwargs) + + +def _to_file( + df, + filename, + driver="ESRI Shapefile", + schema=None, + index=None, + mode="w", + crs=None, + **kwargs +): """ Write this GeoDataFrame to an OGR data source @@ -156,6 +204,18 @@ .. versionadded:: 0.7 Previously the index was not written. + mode : string, default 'w' + The write mode, 'w' to overwrite the existing file and 'a' to append. + Not all drivers support appending. The drivers that support appending + are listed in fiona.supported_drivers or + https://github.com/Toblerity/Fiona/blob/master/fiona/drvsupport.py + crs : pyproj.CRS, default None + If specified, the CRS is passed to Fiona to + better control how the file is written. If None, GeoPandas + will determine the crs based on crs df attribute. + The value can be anything accepted + by :meth:`pyproj.CRS.from_user_input() `, + such as an authority string (eg "EPSG:4326") or a WKT string. The *kwargs* are passed to fiona.open and can be used to write to multi-layer data, store data within archives (zip files), etc. @@ -177,18 +237,22 @@ df = df.reset_index(drop=False) if schema is None: schema = infer_schema(df) + if crs: + crs = pyproj.CRS.from_user_input(crs) + else: + crs = df.crs with fiona_env(): crs_wkt = None try: gdal_version = fiona.env.get_gdal_release_name() except AttributeError: gdal_version = "2.0.0" # just assume it is not the latest - if LooseVersion(gdal_version) >= LooseVersion("3.0.0") and df.crs: - crs_wkt = df.crs.to_wkt() - elif df.crs: - crs_wkt = df.crs.to_wkt("WKT1_GDAL") + if LooseVersion(gdal_version) >= LooseVersion("3.0.0") and crs: + crs_wkt = crs.to_wkt() + elif crs: + crs_wkt = crs.to_wkt("WKT1_GDAL") with fiona.open( - filename, "w", driver=driver, crs_wkt=crs_wkt, schema=schema, **kwargs + filename, mode=mode, driver=driver, crs_wkt=crs_wkt, schema=schema, **kwargs ) as colxn: colxn.writerecords(df.iterfeatures()) diff -Nru python-geopandas-0.7.0/geopandas/io/sql.py python-geopandas-0.8.1/geopandas/io/sql.py --- python-geopandas-0.7.0/geopandas/io/sql.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/io/sql.py 2020-07-15 17:54:36.000000000 +0000 @@ -1,4 +1,4 @@ -import sys +import warnings import pandas as pd @@ -6,8 +6,64 @@ from geopandas import GeoDataFrame +from .. import _compat as compat -def read_postgis( + +def _df_to_geodf(df, geom_col="geom", crs=None): + """ + Transforms a pandas DataFrame into a GeoDataFrame. + The column 'geom_col' must be a geometry column in WKB representation. + To be used to convert df based on pd.read_sql to gdf. + Parameters + ---------- + df : DataFrame + pandas DataFrame with geometry column in WKB representation. + geom_col : string, default 'geom' + column name to convert to shapely geometries + crs : pyproj.CRS, optional + CRS to use for the returned GeoDataFrame. The value can be anything accepted + by :meth:`pyproj.CRS.from_user_input() `, + such as an authority string (eg "EPSG:4326") or a WKT string. + If not set, tries to determine CRS from the SRID associated with the + first geometry in the database, and assigns that to all geometries. + Returns + ------- + GeoDataFrame + """ + + if geom_col not in df: + raise ValueError("Query missing geometry column '{}'".format(geom_col)) + + geoms = df[geom_col].dropna() + + if not geoms.empty: + load_geom_bytes = shapely.wkb.loads + """Load from Python 3 binary.""" + + def load_geom_buffer(x): + """Load from Python 2 binary.""" + return shapely.wkb.loads(str(x)) + + def load_geom_text(x): + """Load from binary encoded as text.""" + return shapely.wkb.loads(str(x), hex=True) + + if isinstance(geoms.iat[0], bytes): + load_geom = load_geom_bytes + else: + load_geom = load_geom_text + + df[geom_col] = geoms = geoms.apply(load_geom) + if crs is None: + srid = shapely.geos.lgeos.GEOSGetSRID(geoms.iat[0]._geom) + # if no defined SRID in geodatabase, returns SRID of 0 + if srid != 0: + crs = "epsg:{}".format(srid) + + return GeoDataFrame(df, crs=crs, geometry=geom_col) + + +def _read_postgis( sql, con, geom_col="geom", @@ -16,6 +72,7 @@ coerce_float=True, parse_dates=None, params=None, + chunksize=None, ): """ Returns a GeoDataFrame corresponding to the result of the query @@ -34,17 +91,20 @@ CRS to use for the returned GeoDataFrame; if not set, tries to determine CRS from the SRID associated with the first geometry in the database, and assigns that to all geometries. + chunksize : int, default None + If specified, return an iterator where chunksize is the number of rows to + include in each chunk. See the documentation for pandas.read_sql for further explanation of the following parameters: - index_col, coerce_float, parse_dates, params + index_col, coerce_float, parse_dates, params, chunksize Returns ------- GeoDataFrame - Example - ------- + Examples + -------- PostGIS >>> sql = "SELECT geom, kind FROM polygons" SpatiaLite @@ -52,47 +112,293 @@ >>> df = geopandas.read_postgis(sql, con) """ - df = pd.read_sql( - sql, - con, - index_col=index_col, - coerce_float=coerce_float, - parse_dates=parse_dates, - params=params, + if chunksize is None: + # read all in one chunk and return a single GeoDataFrame + df = pd.read_sql( + sql, + con, + index_col=index_col, + coerce_float=coerce_float, + parse_dates=parse_dates, + params=params, + chunksize=chunksize, + ) + return _df_to_geodf(df, geom_col=geom_col, crs=crs) + + else: + # read data in chunks and return a generator + df_generator = pd.read_sql( + sql, + con, + index_col=index_col, + coerce_float=coerce_float, + parse_dates=parse_dates, + params=params, + chunksize=chunksize, + ) + return (_df_to_geodf(df, geom_col=geom_col, crs=crs) for df in df_generator) + + +def read_postgis(*args, **kwargs): + import warnings + + warnings.warn( + "geopandas.io.sql.read_postgis() is intended for internal " + "use only, and will be deprecated. Use geopandas.read_postgis() instead.", + DeprecationWarning, + stacklevel=2, ) - if geom_col not in df: - raise ValueError("Query missing geometry column '{}'".format(geom_col)) + return _read_postgis(*args, **kwargs) - geoms = df[geom_col].dropna() - if not geoms.empty: - load_geom_bytes = shapely.wkb.loads - """Load from Python 3 binary.""" +def _get_geometry_type(gdf): + """ + Get basic geometry type of a GeoDataFrame. See more info from: + https://geoalchemy-2.readthedocs.io/en/latest/types.html#geoalchemy2.types._GISType - def load_geom_buffer(x): - """Load from Python 2 binary.""" - return shapely.wkb.loads(str(x)) + Following rules apply: + - if geometries all share the same geometry-type, + geometries are inserted with the given GeometryType with following types: + - Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, + GeometryCollection. + - LinearRing geometries will be converted into LineString -objects. + - in all other cases, geometries will be inserted with type GEOMETRY: + - a mix of Polygons and MultiPolygons in GeoSeries + - a mix of Points and LineStrings in GeoSeries + - geometry is of type GeometryCollection, + such as GeometryCollection([Point, LineStrings]) + - if any of the geometries has Z-coordinate, all records will + be written with 3D. + """ + geom_types = list(gdf.geometry.geom_type.unique()) + has_curve = False + + for gt in geom_types: + if gt is None: + continue + elif "LinearRing" in gt: + has_curve = True + + if len(geom_types) == 1: + if has_curve: + target_geom_type = "LINESTRING" + else: + if geom_types[0] is None: + raise ValueError("No valid geometries in the data.") + else: + target_geom_type = geom_types[0].upper() + else: + target_geom_type = "GEOMETRY" - def load_geom_text(x): - """Load from binary encoded as text.""" - return shapely.wkb.loads(str(x), hex=True) + # Check for 3D-coordinates + if any(gdf.geometry.has_z): + target_geom_type = target_geom_type + "Z" + + return target_geom_type, has_curve + + +def _get_srid_from_crs(gdf): + """ + Get EPSG code from CRS if available. If not, return -1. + """ + + # Use geoalchemy2 default for srid + # Note: undefined srid in PostGIS is 0 + srid = -1 + warning_msg = ( + "Could not parse CRS from the GeoDataFrame. " + + "Inserting data without defined CRS.", + ) + if gdf.crs is not None: + try: + srid = gdf.crs.to_epsg(min_confidence=25) + if srid is None: + srid = -1 + warnings.warn(warning_msg, UserWarning, stacklevel=2) + except Exception: + warnings.warn(warning_msg, UserWarning, stacklevel=2) + return srid + + +def _convert_linearring_to_linestring(gdf, geom_name): + from shapely.geometry import LineString + + # Todo: Use Pygeos function once it's implemented: + # https://github.com/pygeos/pygeos/issues/76 + + mask = gdf.geom_type == "LinearRing" + gdf.loc[mask, geom_name] = gdf.loc[mask, geom_name].apply( + lambda geom: LineString(geom) + ) + return gdf + + +def _convert_to_ewkb(gdf, geom_name, srid): + """Convert geometries to ewkb. """ + if compat.USE_PYGEOS: + from pygeos import set_srid, to_wkb + + geoms = to_wkb( + set_srid(gdf[geom_name].values.data, srid=srid), hex=True, include_srid=True + ) + + else: + from shapely.wkb import dumps + + geoms = [dumps(geom, srid=srid, hex=True) for geom in gdf[geom_name]] + + # The gdf will warn that the geometry column doesn't hold in-memory geometries + # now that they are EWKB, so convert back to a regular dataframe to avoid warning + # the user that the dtypes are unexpected. + df = pd.DataFrame(gdf, copy=False) + df[geom_name] = geoms + return df - if sys.version_info.major < 3: - if isinstance(geoms.iat[0], buffer): - load_geom = load_geom_buffer + +def _psql_insert_copy(tbl, conn, keys, data_iter): + import io + import csv + + s_buf = io.StringIO() + writer = csv.writer(s_buf) + writer.writerows(data_iter) + s_buf.seek(0) + + columns = ", ".join('"{}"'.format(k) for k in keys) + + dbapi_conn = conn.connection + with dbapi_conn.cursor() as cur: + sql = "COPY {} ({}) FROM STDIN WITH CSV".format(tbl.table.fullname, columns) + cur.copy_expert(sql=sql, file=s_buf) + + +def _write_postgis( + gdf, + name, + con, + schema=None, + if_exists="fail", + index=False, + index_label=None, + chunksize=None, + dtype=None, +): + """ + Upload GeoDataFrame into PostGIS database. + + This method requires SQLAlchemy and GeoAlchemy2, and a PostgreSQL + Python driver (e.g. psycopg2) to be installed. + + Parameters + ---------- + name : str + Name of the target table. + con : sqlalchemy.engine.Engine + Active connection to the PostGIS database. + if_exists : {'fail', 'replace', 'append'}, default 'fail' + How to behave if the table already exists: + + - fail: Raise a ValueError. + - replace: Drop the table before inserting new values. + - append: Insert new values to the existing table. + schema : string, optional + Specify the schema. If None, use default schema: 'public'. + index : bool, default True + Write DataFrame index as a column. + Uses *index_label* as the column name in the table. + index_label : string or sequence, default None + Column label for index column(s). + If None is given (default) and index is True, + then the index names are used. + chunksize : int, optional + Rows will be written in batches of this size at a time. + By default, all rows will be written at once. + dtype : dict of column name to SQL type, default None + Specifying the datatype for columns. + The keys should be the column names and the values + should be the SQLAlchemy types. + + Examples + -------- + + >>> from sqlalchemy import create_engine + >>> engine = create_engine("postgres://myusername:mypassword@myhost:5432\ +/mydatabase";) + >>> gdf.to_postgis("my_table", engine) + """ + try: + from geoalchemy2 import Geometry + except ImportError: + raise ImportError("'to_postgis()' requires geoalchemy2 package. ") + + if not compat.SHAPELY_GE_17: + raise ImportError( + "'to_postgis()' requires newer version of Shapely " + "(>= '1.7.0').\nYou can update the library using " + "'pip install shapely --upgrade' or using " + "'conda update shapely' if using conda package manager." + ) + + gdf = gdf.copy() + geom_name = gdf.geometry.name + + # Get srid + srid = _get_srid_from_crs(gdf) + + # Get geometry type and info whether data contains LinearRing. + geometry_type, has_curve = _get_geometry_type(gdf) + + # Build dtype with Geometry + if dtype is not None: + dtype[geom_name] = Geometry(geometry_type=geometry_type, srid=srid) + else: + dtype = {geom_name: Geometry(geometry_type=geometry_type, srid=srid)} + + # Convert LinearRing geometries to LineString + if has_curve: + gdf = _convert_linearring_to_linestring(gdf, geom_name) + + # Convert geometries to EWKB + gdf = _convert_to_ewkb(gdf, geom_name, srid) + + if if_exists == "append": + # Check that the geometry srid matches with the current GeoDataFrame + with con.begin() as connection: + if schema is not None: + schema_name = schema else: - load_geom = load_geom_text - elif isinstance(geoms.iat[0], bytes): - load_geom = load_geom_bytes - else: - load_geom = load_geom_text + schema_name = "public" - df[geom_col] = geoms = geoms.apply(load_geom) - if crs is None: - srid = shapely.geos.lgeos.GEOSGetSRID(geoms.iat[0]._geom) - # if no defined SRID in geodatabase, returns SRID of 0 - if srid != 0: - crs = "epsg:{}".format(srid) + # Only check SRID if table exists + if connection.run_callable(connection.dialect.has_table, name, schema): + target_srid = connection.execute( + "SELECT Find_SRID('{schema}', '{table}', '{geom_col}');".format( + schema=schema_name, table=name, geom_col=geom_name + ) + ).fetchone()[0] + + if target_srid != srid: + msg = ( + "The CRS of the target table (EPSG:{epsg_t}) differs from the " + "CRS of current GeoDataFrame (EPSG:{epsg_src}).".format( + epsg_t=target_srid, epsg_src=srid + ) + ) + raise ValueError(msg) + + with con.begin() as connection: + + gdf.to_sql( + name, + connection, + schema=schema, + if_exists=if_exists, + index=index, + index_label=index_label, + chunksize=chunksize, + dtype=dtype, + method=_psql_insert_copy, + ) - return GeoDataFrame(df, crs=crs, geometry=geom_col) + return Binary files /tmp/tmptG7gKQ/wfN1ju2z2a/python-geopandas-0.7.0/geopandas/io/tests/data/pickle/0.5.1_pd-0.25.3_py-3.7.3_x86_64_linux.pickle and /tmp/tmptG7gKQ/pTopuwFY4B/python-geopandas-0.8.1/geopandas/io/tests/data/pickle/0.5.1_pd-0.25.3_py-3.7.3_x86_64_linux.pickle differ Binary files /tmp/tmptG7gKQ/wfN1ju2z2a/python-geopandas-0.7.0/geopandas/io/tests/data/pickle/0.6.3_pd-0.25.3_py-3.8.0_x86_64_linux.pickle and /tmp/tmptG7gKQ/pTopuwFY4B/python-geopandas-0.8.1/geopandas/io/tests/data/pickle/0.6.3_pd-0.25.3_py-3.8.0_x86_64_linux.pickle differ Binary files /tmp/tmptG7gKQ/wfN1ju2z2a/python-geopandas-0.7.0/geopandas/io/tests/data/pickle/0.7.0_pd-1.0.4_py-3.7.6_x86_64_linux.pickle and /tmp/tmptG7gKQ/pTopuwFY4B/python-geopandas-0.8.1/geopandas/io/tests/data/pickle/0.7.0_pd-1.0.4_py-3.7.6_x86_64_linux.pickle differ Binary files /tmp/tmptG7gKQ/wfN1ju2z2a/python-geopandas-0.7.0/geopandas/io/tests/data/pickle/0.8.0_pd-1.0.5_py-3.8.3_x86_64_linux.pickle and /tmp/tmptG7gKQ/pTopuwFY4B/python-geopandas-0.8.1/geopandas/io/tests/data/pickle/0.8.0_pd-1.0.5_py-3.8.3_x86_64_linux.pickle differ diff -Nru python-geopandas-0.7.0/geopandas/io/tests/generate_legacy_storage_files.py python-geopandas-0.8.1/geopandas/io/tests/generate_legacy_storage_files.py --- python-geopandas-0.7.0/geopandas/io/tests/generate_legacy_storage_files.py 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/io/tests/generate_legacy_storage_files.py 2020-07-15 17:54:36.000000000 +0000 @@ -0,0 +1,98 @@ +""" +Script to create the data and write legacy storage (pickle) files. + +Based on pandas' generate_legacy_storage_files.py script. + +To use this script, create an environment for which you want to +generate pickles, activate the environment, and run this script as: + +$ python geopandas/geopandas/io/tests/generate_legacy_storage_files.py \ + geopandas/geopandas/io/tests/data/pickle/ pickle + +This script generates a storage file for the current arch, system, + +The idea here is you are using the *current* version of the +generate_legacy_storage_files with an *older* version of geopandas to +generate a pickle file. We will then check this file into a current +branch, and test using test_pickle.py. This will load the *older* +pickles and test versus the current data that is generated +(with master). These are then compared. + +""" +import os +import pickle +import platform +import sys + +import pandas as pd + +import geopandas +from shapely.geometry import Point + + +def create_pickle_data(): + """ create the pickle data """ + + # custom geometry column name + gdf_the_geom = geopandas.GeoDataFrame( + {"a": [1, 2, 3], "the_geom": [Point(1, 1), Point(2, 2), Point(3, 3)]}, + geometry="the_geom", + ) + + # with crs + gdf_crs = geopandas.GeoDataFrame( + {"a": [0.1, 0.2, 0.3], "geometry": [Point(1, 1), Point(2, 2), Point(3, 3)]}, + crs="EPSG:4326", + ) + + return dict(gdf_the_geom=gdf_the_geom, gdf_crs=gdf_crs) + + +def platform_name(): + return "_".join( + [ + str(geopandas.__version__), + "pd-" + str(pd.__version__), + "py-" + str(platform.python_version()), + str(platform.machine()), + str(platform.system().lower()), + ] + ) + + +def write_legacy_pickles(output_dir): + print( + "This script generates a storage file for the current arch, system, " + "and python version" + ) + print("geopandas version: {}").format(geopandas.__version__) + print(" output dir : {}".format(output_dir)) + print(" storage format: pickle") + + pth = "{}.pickle".format(platform_name()) + + fh = open(os.path.join(output_dir, pth), "wb") + pickle.dump(create_pickle_data(), fh, pickle.DEFAULT_PROTOCOL) + fh.close() + + print("created pickle file: {}".format(pth)) + + +def main(): + if len(sys.argv) != 3: + exit( + "Specify output directory and storage type: generate_legacy_" + "storage_files.py " + ) + + output_dir = str(sys.argv[1]) + storage_type = str(sys.argv[2]) + + if storage_type == "pickle": + write_legacy_pickles(output_dir=output_dir) + else: + exit("storage_type must be one of {'pickle'}") + + +if __name__ == "__main__": + main() diff -Nru python-geopandas-0.7.0/geopandas/io/tests/test_arrow.py python-geopandas-0.8.1/geopandas/io/tests/test_arrow.py --- python-geopandas-0.7.0/geopandas/io/tests/test_arrow.py 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/io/tests/test_arrow.py 2020-07-15 17:54:36.000000000 +0000 @@ -0,0 +1,511 @@ +from __future__ import absolute_import + +from distutils.version import LooseVersion +import os + +import pytest +from pandas import DataFrame, read_parquet as pd_read_parquet +from pandas.testing import assert_frame_equal +import numpy as np + +import geopandas +from geopandas import GeoDataFrame, read_file, read_parquet, read_feather +from geopandas.array import to_wkb +from geopandas.datasets import get_path +from geopandas.io.arrow import ( + _create_metadata, + _decode_metadata, + _encode_metadata, + _encode_wkb, + _validate_dataframe, + _validate_metadata, + METADATA_VERSION, +) +from geopandas.testing import assert_geodataframe_equal, assert_geoseries_equal + + +# Skip all tests in this module if pyarrow is not available +pyarrow = pytest.importorskip("pyarrow") + +# TEMPORARY: hide warning from to_parquet +pytestmark = pytest.mark.filterwarnings("ignore:.*initial implementation of Parquet.*") + + +@pytest.fixture( + params=[ + "parquet", + pytest.param( + "feather", + marks=pytest.mark.skipif( + pyarrow.__version__ < LooseVersion("0.17.0"), + reason="needs pyarrow >= 0.17", + ), + ), + ] +) +def file_format(request): + if request.param == "parquet": + return read_parquet, GeoDataFrame.to_parquet + elif request.param == "feather": + return read_feather, GeoDataFrame.to_feather + + +def test_create_metadata(): + test_dataset = "naturalearth_lowres" + df = read_file(get_path(test_dataset)) + metadata = _create_metadata(df) + + assert isinstance(metadata, dict) + assert metadata["schema_version"] == METADATA_VERSION + assert metadata["creator"]["library"] == "geopandas" + assert metadata["creator"]["version"] == geopandas.__version__ + assert metadata["primary_column"] == "geometry" + assert "geometry" in metadata["columns"] + assert metadata["columns"]["geometry"]["crs"] == df.geometry.crs.to_wkt() + assert metadata["columns"]["geometry"]["encoding"] == "WKB" + + assert np.array_equal( + metadata["columns"]["geometry"]["bbox"], df.geometry.total_bounds + ) + + +def test_encode_metadata(): + metadata = {"a": "b"} + + expected = b'{"a": "b"}' + assert _encode_metadata(metadata) == expected + + +def test_decode_metadata(): + metadata_str = b'{"a": "b"}' + + expected = {"a": "b"} + assert _decode_metadata(metadata_str) == expected + + +def test_validate_dataframe(): + test_dataset = "naturalearth_lowres" + df = read_file(get_path(test_dataset)) + + # valid: should not raise ValueError + _validate_dataframe(df) + _validate_dataframe(df.set_index("iso_a3")) + + # add column with non-string type + df[0] = 1 + + # invalid: should raise ValueError + with pytest.raises(ValueError): + _validate_dataframe(df) + + with pytest.raises(ValueError): + _validate_dataframe(df.set_index(0)) + + # not a DataFrame: should raise ValueError + with pytest.raises(ValueError): + _validate_dataframe("not a dataframe") + + +def test_validate_metadata_valid(): + _validate_metadata( + { + "primary_column": "geometry", + "columns": {"geometry": {"crs": None, "encoding": "WKB"}}, + } + ) + + _validate_metadata( + { + "primary_column": "geometry", + "columns": {"geometry": {"crs": "WKT goes here", "encoding": "WKB"}}, + } + ) + + +@pytest.mark.parametrize( + "metadata,error", + [ + ({}, "Missing or malformed geo metadata in Parquet/Feather file"), + ( + {"primary_column": "foo"}, + "'geo' metadata in Parquet/Feather file is missing required key:", + ), + ( + {"primary_column": "foo", "columns": None}, + "'geo' metadata in Parquet/Feather file is missing required key", + ), + ( + {"primary_column": "foo", "columns": []}, + "'columns' in 'geo' metadata must be a dict", + ), + ( + {"primary_column": "foo", "columns": {"foo": {}}}, + ( + "'geo' metadata in Parquet/Feather file is missing required key 'crs' " + "for column 'foo'" + ), + ), + ( + {"primary_column": "foo", "columns": {"foo": {"crs": None}}}, + "'geo' metadata in Parquet/Feather file is missing required key", + ), + ( + {"primary_column": "foo", "columns": {"foo": {"encoding": None}}}, + "'geo' metadata in Parquet/Feather file is missing required key", + ), + ( + { + "primary_column": "foo", + "columns": {"foo": {"crs": None, "encoding": None}}, + }, + "Only WKB geometry encoding is supported", + ), + ( + { + "primary_column": "foo", + "columns": {"foo": {"crs": None, "encoding": "BKW"}}, + }, + "Only WKB geometry encoding is supported", + ), + ], +) +def test_validate_metadata_invalid(metadata, error): + with pytest.raises(ValueError, match=error): + _validate_metadata(metadata) + + +def test_encode_wkb(): + test_dataset = "naturalearth_lowres" + df = read_file(get_path(test_dataset)) + + encoded = _encode_wkb(df) + + # make sure original is not modified + assert isinstance(df, GeoDataFrame) + assert ( + encoded.geometry.iloc[0][:16] + == b"\x01\x06\x00\x00\x00\x03\x00\x00\x00\x01\x03\x00\x00\x00\x01\x00" + ) + + +# TEMPORARY: used to determine if pyarrow fails for roundtripping pandas data +# without geometries +def test_pandas_parquet_roundtrip1(tmpdir): + df = DataFrame({"a": [1, 2, 3], "b": ["a", "b", "c"]}) + + filename = os.path.join(str(tmpdir), "test.pq") + df.to_parquet(filename) + + pq_df = pd_read_parquet(filename) + + assert_frame_equal(df, pq_df) + + +@pytest.mark.parametrize( + "test_dataset", ["naturalearth_lowres", "naturalearth_cities", "nybb"] +) +def test_pandas_parquet_roundtrip2(test_dataset, tmpdir): + test_dataset = "naturalearth_lowres" + df = DataFrame(read_file(get_path(test_dataset)).drop(columns=["geometry"])) + + filename = os.path.join(str(tmpdir), "test.pq") + df.to_parquet(filename) + + pq_df = pd_read_parquet(filename) + + assert_frame_equal(df, pq_df) + + +@pytest.mark.parametrize( + "test_dataset", ["naturalearth_lowres", "naturalearth_cities", "nybb"] +) +def test_roundtrip(tmpdir, file_format, test_dataset): + """Writing to parquet should not raise errors, and should not alter original + GeoDataFrame + """ + reader, writer = file_format + + df = read_file(get_path(test_dataset)) + orig = df.copy() + + filename = os.path.join(str(tmpdir), "test.pq") + + # TEMP: Initial implementation should raise a UserWarning + with pytest.warns(UserWarning, match="initial implementation"): + writer(df, filename) + + assert os.path.exists(filename) + + # make sure that the original data frame is unaltered + assert_geodataframe_equal(df, orig) + + # make sure that we can roundtrip the data frame + pq_df = reader(filename) + + assert isinstance(pq_df, GeoDataFrame) + assert_geodataframe_equal(df, pq_df) + + +def test_index(tmpdir, file_format): + """Setting index=`True` should preserve index in output, and + setting index=`False` should drop index from output. + """ + reader, writer = file_format + + test_dataset = "naturalearth_lowres" + df = read_file(get_path(test_dataset)).set_index("iso_a3") + + filename = os.path.join(str(tmpdir), "test_with_index.pq") + writer(df, filename, index=True) + pq_df = reader(filename) + assert_geodataframe_equal(df, pq_df) + + filename = os.path.join(str(tmpdir), "drop_index.pq") + writer(df, filename, index=False) + pq_df = reader(filename) + assert_geodataframe_equal(df.reset_index(drop=True), pq_df) + + +@pytest.mark.parametrize("compression", ["snappy", "gzip", "brotli", None]) +def test_parquet_compression(compression, tmpdir): + """Using compression options should not raise errors, and should + return identical GeoDataFrame. + """ + + test_dataset = "naturalearth_lowres" + df = read_file(get_path(test_dataset)) + + filename = os.path.join(str(tmpdir), "test.pq") + df.to_parquet(filename, compression=compression) + pq_df = read_parquet(filename) + + assert isinstance(pq_df, GeoDataFrame) + assert_geodataframe_equal(df, pq_df) + + +@pytest.mark.skipif( + pyarrow.__version__ < LooseVersion("0.17.0"), + reason="Feather only supported for pyarrow >= 0.17", +) +@pytest.mark.parametrize("compression", ["uncompressed", "lz4", "zstd"]) +def test_feather_compression(compression, tmpdir): + """Using compression options should not raise errors, and should + return identical GeoDataFrame. + """ + + test_dataset = "naturalearth_lowres" + df = read_file(get_path(test_dataset)) + + filename = os.path.join(str(tmpdir), "test.feather") + df.to_feather(filename, compression=compression) + pq_df = read_feather(filename) + + assert isinstance(pq_df, GeoDataFrame) + assert_geodataframe_equal(df, pq_df) + + +def test_parquet_multiple_geom_cols(tmpdir, file_format): + """If multiple geometry columns are present when written to parquet, + they should all be returned as such when read from parquet. + """ + reader, writer = file_format + + test_dataset = "naturalearth_lowres" + df = read_file(get_path(test_dataset)) + df["geom2"] = df.geometry.copy() + + filename = os.path.join(str(tmpdir), "test.pq") + writer(df, filename) + + assert os.path.exists(filename) + + pq_df = reader(filename) + + assert isinstance(pq_df, GeoDataFrame) + assert_geodataframe_equal(df, pq_df) + + assert_geoseries_equal(df.geom2, pq_df.geom2, check_geom_type=True) + + +def test_parquet_missing_metadata(tmpdir): + """Missing geo metadata, such as from a parquet file created + from a pandas DataFrame, will raise a ValueError. + """ + + test_dataset = "naturalearth_lowres" + df = read_file(get_path(test_dataset)) + + # convert to DataFrame + df = DataFrame(df) + + # convert the geometry column so we can extract later + df["geometry"] = to_wkb(df["geometry"].values) + + filename = os.path.join(str(tmpdir), "test.pq") + + # use pandas to_parquet (no geo metadata) + df.to_parquet(filename) + + # missing metadata will raise ValueError + with pytest.raises( + ValueError, match="Missing geo metadata in Parquet/Feather file." + ): + read_parquet(filename) + + +@pytest.mark.parametrize( + "geo_meta,error", + [ + ({"geo": b""}, "Missing or malformed geo metadata in Parquet/Feather file"), + ( + {"geo": _encode_metadata({})}, + "Missing or malformed geo metadata in Parquet/Feather file", + ), + ( + {"geo": _encode_metadata({"foo": "bar"})}, + "'geo' metadata in Parquet/Feather file is missing required key", + ), + ], +) +def test_parquet_invalid_metadata(tmpdir, geo_meta, error): + """Has geo metadata with missing required fields will raise a ValueError. + + This requires writing the parquet file directly below, so that we can + control the metadata that is written for this test. + """ + + from pyarrow import parquet, Table + + test_dataset = "naturalearth_lowres" + df = read_file(get_path(test_dataset)) + + # convert to DataFrame and encode geometry to WKB + df = DataFrame(df) + df["geometry"] = to_wkb(df["geometry"].values) + + table = Table.from_pandas(df) + metadata = table.schema.metadata + metadata.update(geo_meta) + table = table.replace_schema_metadata(metadata) + + filename = os.path.join(str(tmpdir), "test.pq") + parquet.write_table(table, filename) + + with pytest.raises(ValueError, match=error): + read_parquet(filename) + + +def test_subset_columns(tmpdir, file_format): + """Reading a subset of columns should correctly decode selected geometry + columns. + """ + reader, writer = file_format + + test_dataset = "naturalearth_lowres" + df = read_file(get_path(test_dataset)) + + filename = os.path.join(str(tmpdir), "test.pq") + writer(df, filename) + pq_df = reader(filename, columns=["name", "geometry"]) + + assert_geodataframe_equal(df[["name", "geometry"]], pq_df) + + with pytest.raises( + ValueError, match="No geometry columns are included in the columns read" + ): + reader(filename, columns=["name"]) + + +def test_parquet_repeat_columns(tmpdir): + """Reading repeated columns should return first value of each repeated column + """ + + test_dataset = "naturalearth_lowres" + df = read_file(get_path(test_dataset)) + + filename = os.path.join(str(tmpdir), "test.pq") + df.to_parquet(filename) + + columns = ["name", "name", "iso_a3", "name", "geometry"] + pq_df = read_parquet(filename, columns=columns) + + assert pq_df.columns.tolist() == ["name", "iso_a3", "geometry"] + + +def test_promote_secondary_geometry(tmpdir, file_format): + """Reading a subset of columns that does not include the primary geometry + column should promote the first geometry column present. + """ + reader, writer = file_format + + test_dataset = "naturalearth_lowres" + df = read_file(get_path(test_dataset)) + df["geom2"] = df.geometry.copy() + + filename = os.path.join(str(tmpdir), "test.pq") + writer(df, filename) + pq_df = reader(filename, columns=["name", "geom2"]) + + assert_geodataframe_equal(df.set_geometry("geom2")[["name", "geom2"]], pq_df) + + df["geom3"] = df.geometry.copy() + + writer(df, filename) + with pytest.warns( + UserWarning, + match="Multiple non-primary geometry columns read from Parquet/Feather file.", + ): + pq_df = reader(filename, columns=["name", "geom2", "geom3"]) + + assert_geodataframe_equal( + df.set_geometry("geom2")[["name", "geom2", "geom3"]], pq_df + ) + + +def test_columns_no_geometry(tmpdir, file_format): + """Reading a parquet file that is missing all of the geometry columns + should raise a ValueError""" + reader, writer = file_format + + test_dataset = "naturalearth_lowres" + df = read_file(get_path(test_dataset)) + + filename = os.path.join(str(tmpdir), "test.pq") + writer(df, filename) + + with pytest.raises(ValueError): + reader(filename, columns=["name"]) + + +def test_missing_crs(tmpdir, file_format): + """If CRS is `None`, it should be properly handled + and remain `None` when read from parquet`. + """ + reader, writer = file_format + + test_dataset = "naturalearth_lowres" + + df = read_file(get_path(test_dataset)) + df.crs = None + + filename = os.path.join(str(tmpdir), "test.pq") + writer(df, filename) + pq_df = reader(filename) + + assert pq_df.crs is None + + assert_geodataframe_equal(df, pq_df, check_crs=True) + + +@pytest.mark.skipif( + pyarrow.__version__ >= LooseVersion("0.17.0"), + reason="Feather only supported for pyarrow >= 0.17", +) +def test_feather_arrow_version(tmpdir): + df = read_file(get_path("naturalearth_lowres")) + filename = os.path.join(str(tmpdir), "test.feather") + + with pytest.raises( + ImportError, match="pyarrow >= 0.17 required for Feather support" + ): + df.to_feather(filename) diff -Nru python-geopandas-0.7.0/geopandas/io/tests/test_file.py python-geopandas-0.8.1/geopandas/io/tests/test_file.py --- python-geopandas-0.7.0/geopandas/io/tests/test_file.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/io/tests/test_file.py 2020-07-15 17:54:36.000000000 +0000 @@ -1,8 +1,10 @@ from collections import OrderedDict import datetime from distutils.version import LooseVersion +import io import os import pathlib +import tempfile import sys import numpy as np @@ -38,6 +40,11 @@ @pytest.fixture +def file_path(): + return os.path.join(PACKAGE_DIR, "examples", "null_geom.geojson") + + +@pytest.fixture def df_points(): N = 10 crs = _CRS @@ -204,6 +211,12 @@ input_empty_df.to_file(tempfilename) +def test_to_file_privacy(tmpdir, df_nybb): + tempfilename = os.path.join(str(tmpdir), "test.shp") + with pytest.warns(DeprecationWarning): + geopandas.io.file.to_file(df_nybb, tempfilename) + + def test_to_file_schema(tmpdir, df_nybb): """ Ensure that the file is written according to the schema @@ -230,6 +243,36 @@ assert result_schema == schema +@pytest.mark.parametrize("driver,ext", driver_ext_pairs) +def test_append_file(tmpdir, df_nybb, df_null, driver, ext): + """ Test to_file with append mode and from_file """ + from fiona import supported_drivers + + if "a" not in supported_drivers[driver]: + return None + + tempfilename = os.path.join(str(tmpdir), "boros." + ext) + df_nybb.to_file(tempfilename, driver=driver) + df_nybb.to_file(tempfilename, mode="a", driver=driver) + # Read layer back in + df = GeoDataFrame.from_file(tempfilename) + assert "geometry" in df + assert len(df) == (5 * 2) + expected = pd.concat([df_nybb] * 2, ignore_index=True) + assert_geodataframe_equal(df, expected) + + # Write layer with null geometry out to file + tempfilename = os.path.join(str(tmpdir), "null_geom." + ext) + df_null.to_file(tempfilename, driver=driver) + df_null.to_file(tempfilename, mode="a", driver=driver) + # Read layer back in + df = GeoDataFrame.from_file(tempfilename) + assert "geometry" in df + assert len(df) == (2 * 2) + expected = pd.concat([df_null] * 2, ignore_index=True) + assert_geodataframe_equal(df, expected) + + # ----------------------------------------------------------------------------- # read_file tests # ----------------------------------------------------------------------------- @@ -259,6 +302,73 @@ assert isinstance(gdf, geopandas.GeoDataFrame) +@pytest.mark.skipif( + not _FIONA18, reason="support for file-like objects in fiona.open() added in 1.8" +) +def test_read_file_textio(file_path): + file_text_stream = open(file_path) + file_stringio = io.StringIO(open(file_path).read()) + gdf_text_stream = read_file(file_text_stream) + gdf_stringio = read_file(file_stringio) + assert isinstance(gdf_text_stream, geopandas.GeoDataFrame) + assert isinstance(gdf_stringio, geopandas.GeoDataFrame) + + +@pytest.mark.skipif( + not _FIONA18, reason="support for file-like objects in fiona.open() added in 1.8" +) +def test_read_file_bytesio(file_path): + file_binary_stream = open(file_path, "rb") + file_bytesio = io.BytesIO(open(file_path, "rb").read()) + gdf_binary_stream = read_file(file_binary_stream) + gdf_bytesio = read_file(file_bytesio) + assert isinstance(gdf_binary_stream, geopandas.GeoDataFrame) + assert isinstance(gdf_bytesio, geopandas.GeoDataFrame) + + +@pytest.mark.skipif( + not _FIONA18, reason="support for file-like objects in fiona.open() added in 1.8" +) +def test_read_file_raw_stream(file_path): + file_raw_stream = open(file_path, "rb", buffering=0) + gdf_raw_stream = read_file(file_raw_stream) + assert isinstance(gdf_raw_stream, geopandas.GeoDataFrame) + + +@pytest.mark.skipif( + not _FIONA18, reason="support for file-like objects in fiona.open() added in 1.8" +) +def test_read_file_pathlib(file_path): + path_object = pathlib.Path(file_path) + gdf_path_object = read_file(path_object) + assert isinstance(gdf_path_object, geopandas.GeoDataFrame) + + +@pytest.mark.skipif( + not _FIONA18, reason="support for file-like objects in fiona.open() added in 1.8" +) +def test_read_file_tempfile(): + temp = tempfile.TemporaryFile() + temp.write( + b""" + { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [0, 0] + }, + "properties": { + "name": "Null Island" + } + } + """ + ) + temp.seek(0) + gdf_tempfile = geopandas.read_file(temp) + assert isinstance(gdf_tempfile, geopandas.GeoDataFrame) + temp.close() + + def test_read_file_filtered(df_nybb): full_df_shape = df_nybb.shape nybb_filename = geopandas.datasets.get_path("nybb") @@ -310,11 +420,35 @@ assert filtered_df_shape == (1, 5) -def read_file_filtered_rows_invalid(): +def test_read_file_filtered_rows_invalid(): with pytest.raises(TypeError): read_file(geopandas.datasets.get_path("nybb"), rows="not_a_slice") +@pytest.mark.skipif( + LooseVersion(fiona.__version__) < LooseVersion("1.8"), + reason="Ignore geometry only available in Fiona 1.8", +) +def test_read_file__ignore_geometry(): + pdf = geopandas.read_file( + geopandas.datasets.get_path("naturalearth_lowres"), ignore_geometry=True, + ) + assert "geometry" not in pdf.columns + assert isinstance(pdf, pd.DataFrame) and not isinstance(pdf, geopandas.GeoDataFrame) + + +@pytest.mark.skipif( + LooseVersion(fiona.__version__) < LooseVersion("1.8"), + reason="Ignore fields only available in Fiona 1.8", +) +def test_read_file__ignore_all_fields(): + gdf = geopandas.read_file( + geopandas.datasets.get_path("naturalearth_lowres"), + ignore_fields=["pop_est", "continent", "name", "iso_a3", "gdp_md_est"], + ) + assert gdf.columns.tolist() == ["geometry"] + + def test_read_file_filtered_with_gdf_boundary(df_nybb): full_df_shape = df_nybb.shape nybb_filename = geopandas.datasets.get_path("nybb") @@ -423,6 +557,11 @@ assert all(empty.columns == ["A", "Z", "geometry"]) +def test_read_file_privacy(tmpdir, df_nybb): + with pytest.warns(DeprecationWarning): + geopandas.io.file.read_file(geopandas.datasets.get_path("nybb")) + + class FileNumber(object): def __init__(self, tmpdir, base, ext): self.tmpdir = str(tmpdir) diff -Nru python-geopandas-0.7.0/geopandas/io/tests/test_pickle.py python-geopandas-0.8.1/geopandas/io/tests/test_pickle.py --- python-geopandas-0.7.0/geopandas/io/tests/test_pickle.py 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/io/tests/test_pickle.py 2020-07-15 17:54:36.000000000 +0000 @@ -0,0 +1,60 @@ +""" +See generate_legacy_storage_files.py for the creation of the legacy files. + +""" +from distutils.version import LooseVersion +import glob +import os +import pathlib + +import pandas as pd + +import pyproj + +import pytest +from geopandas.testing import assert_geodataframe_equal +from geopandas import _compat as compat + + +DATA_PATH = pathlib.Path(os.path.dirname(__file__)) / "data" + + +@pytest.fixture(scope="module") +def current_pickle_data(): + # our current version pickle data + from .generate_legacy_storage_files import create_pickle_data + + return create_pickle_data() + + +files = glob.glob(str(DATA_PATH / "pickle" / "*.pickle")) + + +@pytest.fixture(params=files, ids=[p.split("/")[-1] for p in files]) +def legacy_pickle(request): + return request.param + + +@pytest.mark.skipif( + compat.USE_PYGEOS or (str(pyproj.__version__) < LooseVersion("2.4")), + reason=( + "pygeos-based unpickling currently only works for pygeos-written files; " + "old pyproj versions can't read pickles from newer pyproj versions" + ), +) +def test_legacy_pickles(current_pickle_data, legacy_pickle): + result = pd.read_pickle(legacy_pickle) + + for name, value in result.items(): + expected = current_pickle_data[name] + assert_geodataframe_equal(value, expected) + + +def test_round_trip_current(tmpdir, current_pickle_data): + data = current_pickle_data + + for name, value in data.items(): + path = str(tmpdir / "{}.pickle".format(name)) + value.to_pickle(path) + result = pd.read_pickle(path) + assert_geodataframe_equal(result, value) diff -Nru python-geopandas-0.7.0/geopandas/io/tests/test_sql.py python-geopandas-0.8.1/geopandas/io/tests/test_sql.py --- python-geopandas-0.7.0/geopandas/io/tests/test_sql.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/io/tests/test_sql.py 2020-07-15 17:54:36.000000000 +0000 @@ -4,17 +4,15 @@ configuration. postGIS tests require a test database to have been setup; see geopandas.tests.util for more information. """ +import os + +import pandas as pd import geopandas -from geopandas import read_file, read_postgis +from geopandas import GeoDataFrame, read_file, read_postgis -from geopandas.tests.util import ( - connect, - connect_spatialite, - create_postgis, - create_spatialite, - validate_boro_df, -) +from geopandas.io.sql import _write_postgis as write_postgis +from geopandas.tests.util import create_postgis, create_spatialite, validate_boro_df import pytest @@ -25,126 +23,614 @@ return df +@pytest.fixture() +def connection_postgis(): + """ + Initiaties a connection to a postGIS database that must already exist. + See create_postgis for more information. + """ + psycopg2 = pytest.importorskip("psycopg2") + from psycopg2 import OperationalError + + dbname = "test_geopandas" + user = os.environ.get("PGUSER") + password = os.environ.get("PGPASSWORD") + host = os.environ.get("PGHOST") + port = os.environ.get("PGPORT") + try: + con = psycopg2.connect( + dbname=dbname, user=user, password=password, host=host, port=port + ) + except OperationalError: + pytest.skip("Cannot connect with postgresql database") + + yield con + con.close() + + +@pytest.fixture() +def engine_postgis(): + """ + Initiaties a connection engine to a postGIS database that must already exist. + """ + sqlalchemy = pytest.importorskip("sqlalchemy") + from sqlalchemy.engine.url import URL + + user = os.environ.get("PGUSER") + password = os.environ.get("PGPASSWORD") + host = os.environ.get("PGHOST") + port = os.environ.get("PGPORT") + dbname = "test_geopandas" + + try: + con = sqlalchemy.create_engine( + URL( + drivername="postgresql+psycopg2", + username=user, + database=dbname, + password=password, + host=host, + port=port, + ) + ) + con.begin() + except Exception: + pytest.skip("Cannot connect with postgresql database") + + yield con + con.dispose() + + +@pytest.fixture() +def connection_spatialite(): + """ + Return a memory-based SQLite3 connection with SpatiaLite enabled & initialized. + + `The sqlite3 module must be built with loadable extension support + `_ and + `SpatiaLite `_ + must be available on the system as a SQLite module. + Packages available on Anaconda meet requirements. + + Exceptions + ---------- + ``AttributeError`` on missing support for loadable SQLite extensions + ``sqlite3.OperationalError`` on missing SpatiaLite + """ + sqlite3 = pytest.importorskip("sqlite3") + try: + with sqlite3.connect(":memory:") as con: + con.enable_load_extension(True) + con.load_extension("mod_spatialite") + con.execute("SELECT InitSpatialMetaData(TRUE)") + except Exception: + con.close() + pytest.skip("Cannot setup spatialite database") + + yield con + con.close() + + +def drop_table_if_exists(engine, table): + sqlalchemy = pytest.importorskip("sqlalchemy") + + if engine.has_table(table): + metadata = sqlalchemy.MetaData(engine) + metadata.reflect() + table = metadata.tables.get(table) + if table is not None: + table.drop(checkfirst=True) + + +@pytest.fixture +def df_mixed_single_and_multi(): + from shapely.geometry import Point, LineString, MultiLineString + + df = geopandas.GeoDataFrame( + { + "geometry": [ + LineString([(0, 0), (1, 1)]), + MultiLineString([[(0, 0), (1, 1)], [(2, 2), (3, 3)]]), + Point(0, 1), + ] + }, + crs="epsg:4326", + ) + return df + + +@pytest.fixture +def df_geom_collection(): + from shapely.geometry import Point, LineString, Polygon, GeometryCollection + + df = geopandas.GeoDataFrame( + { + "geometry": [ + GeometryCollection( + [ + Polygon([(0, 0), (1, 1), (0, 1)]), + LineString([(0, 0), (1, 1)]), + Point(0, 0), + ] + ) + ] + }, + crs="epsg:4326", + ) + return df + + +@pytest.fixture +def df_linear_ring(): + from shapely.geometry import LinearRing + + df = geopandas.GeoDataFrame( + {"geometry": [LinearRing(((0, 0), (0, 1), (1, 1), (1, 0)))]}, crs="epsg:4326" + ) + return df + + +@pytest.fixture +def df_3D_geoms(): + from shapely.geometry import Point, LineString, Polygon + + df = geopandas.GeoDataFrame( + { + "geometry": [ + LineString([(0, 0, 0), (1, 1, 1)]), + Polygon([(0, 0, 0), (1, 1, 1), (0, 1, 1)]), + Point(0, 1, 2), + ] + }, + crs="epsg:4326", + ) + return df + + class TestIO: - def test_read_postgis_default(self, df_nybb): - con = connect("test_geopandas") - if con is None or not create_postgis(df_nybb): - raise pytest.skip() + def test_read_postgis_default(self, connection_postgis, df_nybb): + con = connection_postgis + create_postgis(con, df_nybb) - try: - sql = "SELECT * FROM nybb;" - df = read_postgis(sql, con) - finally: - con.close() + sql = "SELECT * FROM nybb;" + df = read_postgis(sql, con) validate_boro_df(df) # no crs defined on the created geodatabase, and none specified # by user; should not be set to 0, as from get_srid failure assert df.crs is None - def test_read_postgis_custom_geom_col(self, df_nybb): - con = connect("test_geopandas") + def test_read_postgis_custom_geom_col(self, connection_postgis, df_nybb): + con = connection_postgis geom_col = "the_geom" - if con is None or not create_postgis(df_nybb, geom_col=geom_col): - raise pytest.skip() + create_postgis(con, df_nybb, geom_col=geom_col) - try: - sql = "SELECT * FROM nybb;" - df = read_postgis(sql, con, geom_col=geom_col) - finally: - con.close() + sql = "SELECT * FROM nybb;" + df = read_postgis(sql, con, geom_col=geom_col) validate_boro_df(df) - def test_read_postgis_select_geom_as(self, df_nybb): + def test_read_postgis_select_geom_as(self, connection_postgis, df_nybb): """Tests that a SELECT {geom} AS {some_other_geom} works.""" - con = connect("test_geopandas") + con = connection_postgis orig_geom = "geom" out_geom = "the_geom" - if con is None or not create_postgis(df_nybb, geom_col=orig_geom): - raise pytest.skip() + create_postgis(con, df_nybb, geom_col=orig_geom) - try: - sql = """SELECT borocode, boroname, shape_leng, shape_area, - {} as {} FROM nybb;""".format( - orig_geom, out_geom - ) - df = read_postgis(sql, con, geom_col=out_geom) - finally: - con.close() + sql = """SELECT borocode, boroname, shape_leng, shape_area, + {} as {} FROM nybb;""".format( + orig_geom, out_geom + ) + df = read_postgis(sql, con, geom_col=out_geom) validate_boro_df(df) - def test_read_postgis_get_srid(self, df_nybb): + def test_read_postgis_get_srid(self, connection_postgis, df_nybb): """Tests that an SRID can be read from a geodatabase (GH #451).""" + con = connection_postgis crs = "epsg:4269" df_reproj = df_nybb.to_crs(crs) - created = create_postgis(df_reproj, srid=4269) - con = connect("test_geopandas") - if con is None or not created: - raise pytest.skip() + create_postgis(con, df_reproj, srid=4269) - try: - sql = "SELECT * FROM nybb;" - df = read_postgis(sql, con) - finally: - con.close() + sql = "SELECT * FROM nybb;" + df = read_postgis(sql, con) validate_boro_df(df) assert df.crs == crs - def test_read_postgis_override_srid(self, df_nybb): + def test_read_postgis_override_srid(self, connection_postgis, df_nybb): """Tests that a user specified CRS overrides the geodatabase SRID.""" + con = connection_postgis orig_crs = df_nybb.crs - created = create_postgis(df_nybb, srid=4269) - con = connect("test_geopandas") - if con is None or not created: - raise pytest.skip() + create_postgis(con, df_nybb, srid=4269) - try: - sql = "SELECT * FROM nybb;" - df = read_postgis(sql, con, crs=orig_crs) - finally: - con.close() + sql = "SELECT * FROM nybb;" + df = read_postgis(sql, con, crs=orig_crs) validate_boro_df(df) assert df.crs == orig_crs - def test_read_postgis_null_geom(self, df_nybb): + def test_from_postgis_default(self, connection_postgis, df_nybb): + con = connection_postgis + create_postgis(con, df_nybb) + + sql = "SELECT * FROM nybb;" + df = GeoDataFrame.from_postgis(sql, con) + + validate_boro_df(df, case_sensitive=False) + + def test_from_postgis_custom_geom_col(self, connection_postgis, df_nybb): + con = connection_postgis + geom_col = "the_geom" + create_postgis(con, df_nybb, geom_col=geom_col) + + sql = "SELECT * FROM nybb;" + df = GeoDataFrame.from_postgis(sql, con, geom_col=geom_col) + + validate_boro_df(df, case_sensitive=False) + + def test_read_postgis_null_geom(self, connection_spatialite, df_nybb): """Tests that geometry with NULL is accepted.""" + con = connection_spatialite + geom_col = df_nybb.geometry.name + df_nybb.geometry.iat[0] = None + create_spatialite(con, df_nybb) + sql = ( + "SELECT ogc_fid, borocode, boroname, shape_leng, shape_area, " + 'AsEWKB("{0}") AS "{0}" FROM nybb'.format(geom_col) + ) + df = read_postgis(sql, con, geom_col=geom_col) + validate_boro_df(df) + + def test_read_postgis_binary(self, connection_spatialite, df_nybb): + """Tests that geometry read as binary is accepted.""" + con = connection_spatialite + geom_col = df_nybb.geometry.name + create_spatialite(con, df_nybb) + sql = ( + "SELECT ogc_fid, borocode, boroname, shape_leng, shape_area, " + 'ST_AsBinary("{0}") AS "{0}" FROM nybb'.format(geom_col) + ) + df = read_postgis(sql, con, geom_col=geom_col) + validate_boro_df(df) + + def test_read_postgis_chunksize(self, connection_postgis, df_nybb): + """Test chunksize argument""" + chunksize = 2 + con = connection_postgis + create_postgis(con, df_nybb) + + sql = "SELECT * FROM nybb;" + df = pd.concat(read_postgis(sql, con, chunksize=chunksize)) + + validate_boro_df(df) + # no crs defined on the created geodatabase, and none specified + # by user; should not be set to 0, as from get_srid failure + assert df.crs is None + + def test_read_postgis_privacy(self, connection_postgis, df_nybb): + con = connection_postgis + create_postgis(con, df_nybb) + + sql = "SELECT * FROM nybb;" + with pytest.warns(DeprecationWarning): + geopandas.io.sql.read_postgis(sql, con) + + def test_write_postgis_default(self, engine_postgis, df_nybb): + """Tests that GeoDataFrame can be written to PostGIS with defaults.""" + engine = engine_postgis + table = "nybb" + + # If table exists, delete it before trying to write with defaults + drop_table_if_exists(engine, table) + + # Write to db + write_postgis(df_nybb, con=engine, name=table, if_exists="fail") + # Validate + sql = "SELECT * FROM {table};".format(table=table) + df = read_postgis(sql, engine, geom_col="geometry") + validate_boro_df(df) + + def test_write_postgis_fail_when_table_exists(self, engine_postgis, df_nybb): + """ + Tests that uploading the same table raises error when: if_replace='fail'. + """ + engine = engine_postgis + + table = "nybb" + + # Ensure table exists + write_postgis(df_nybb, con=engine, name=table, if_exists="replace") + try: - con = connect_spatialite() - except Exception: - raise pytest.skip() - else: - geom_col = df_nybb.geometry.name - df_nybb.geometry.iat[0] = None - create_spatialite(con, df_nybb) - sql = ( - "SELECT ogc_fid, borocode, boroname, shape_leng, shape_area, " - 'AsEWKB("{0}") AS "{0}" FROM nybb'.format(geom_col) + write_postgis(df_nybb, con=engine, name=table, if_exists="fail") + except ValueError as e: + if "already exists" in str(e): + pass + else: + raise e + + def test_write_postgis_replace_when_table_exists(self, engine_postgis, df_nybb): + """ + Tests that replacing a table is possible when: if_replace='replace'. + """ + engine = engine_postgis + + table = "nybb" + + # Ensure table exists + write_postgis(df_nybb, con=engine, name=table, if_exists="replace") + # Overwrite + write_postgis(df_nybb, con=engine, name=table, if_exists="replace") + # Validate + sql = "SELECT * FROM {table};".format(table=table) + df = read_postgis(sql, engine, geom_col="geometry") + validate_boro_df(df) + + def test_write_postgis_append_when_table_exists(self, engine_postgis, df_nybb): + """ + Tests that appending to existing table produces correct results when: + if_replace='append'. + """ + engine = engine_postgis + + table = "nybb" + + orig_rows, orig_cols = df_nybb.shape + write_postgis(df_nybb, con=engine, name=table, if_exists="replace") + write_postgis(df_nybb, con=engine, name=table, if_exists="append") + # Validate + sql = "SELECT * FROM {table};".format(table=table) + df = read_postgis(sql, engine, geom_col="geometry") + new_rows, new_cols = df.shape + + # There should be twice as many rows in the new table + assert new_rows == orig_rows * 2, ( + "There should be {target} rows," + "found: {current}".format(target=orig_rows * 2, current=new_rows), + ) + # Number of columns should stay the same + assert new_cols == orig_cols, ( + "There should be {target} columns," + "found: {current}".format(target=orig_cols, current=new_cols), + ) + + def test_write_postgis_without_crs(self, engine_postgis, df_nybb): + """ + Tests that GeoDataFrame can be written to PostGIS without CRS information. + """ + engine = engine_postgis + + table = "nybb" + + # Write to db + df_nybb = df_nybb + df_nybb.crs = None + write_postgis(df_nybb, con=engine, name=table, if_exists="replace") + # Validate that srid is -1 + target_srid = engine.execute( + "SELECT Find_SRID('{schema}', '{table}', '{geom_col}');".format( + schema="public", table=table, geom_col="geometry" ) - df = read_postgis(sql, con, geom_col=geom_col) - validate_boro_df(df) - finally: - if "con" in locals(): - con.close() + ).fetchone()[0] + assert target_srid == 0, "SRID should be 0, found %s" % target_srid + + def test_write_postgis_geometry_collection( + self, engine_postgis, df_geom_collection + ): + """ + Tests that writing a mix of different geometry types is possible. + """ + engine = engine_postgis + + table = "geomtype_tests" + + write_postgis(df_geom_collection, con=engine, name=table, if_exists="replace") + + # Validate geometry type + sql = "SELECT DISTINCT(GeometryType(geometry)) FROM {table} ORDER BY 1;".format( + table=table + ) + geom_type = engine.execute(sql).fetchone()[0] + sql = "SELECT * FROM {table};".format(table=table) + df = read_postgis(sql, engine, geom_col="geometry") + + assert geom_type.upper() == "GEOMETRYCOLLECTION" + assert df.geom_type.unique()[0] == "GeometryCollection" + + def test_write_postgis_mixed_geometry_types( + self, engine_postgis, df_mixed_single_and_multi + ): + """ + Tests that writing a mix of single and MultiGeometries is possible. + """ + engine = engine_postgis + + table = "geomtype_tests" + + write_postgis( + df_mixed_single_and_multi, con=engine, name=table, if_exists="replace" + ) + + # Validate geometry type + sql = "SELECT DISTINCT GeometryType(geometry) FROM {table} ORDER BY 1;".format( + table=table + ) + res = engine.execute(sql).fetchall() + assert res[0][0].upper() == "LINESTRING" + assert res[1][0].upper() == "MULTILINESTRING" + assert res[2][0].upper() == "POINT" + + def test_write_postgis_linear_ring(self, engine_postgis, df_linear_ring): + """ + Tests that writing a LinearRing. + """ + engine = engine_postgis + + table = "geomtype_tests" + + write_postgis(df_linear_ring, con=engine, name=table, if_exists="replace") + + # Validate geometry type + sql = "SELECT DISTINCT(GeometryType(geometry)) FROM {table} ORDER BY 1;".format( + table=table + ) + geom_type = engine.execute(sql).fetchone()[0] + + assert geom_type.upper() == "LINESTRING" + + def test_write_postgis_in_chunks(self, engine_postgis, df_mixed_single_and_multi): + """ + Tests writing a LinearRing works. + """ + engine = engine_postgis + + table = "geomtype_tests" + + write_postgis( + df_mixed_single_and_multi, + con=engine, + name=table, + if_exists="replace", + chunksize=1, + ) + # Validate row count + sql = "SELECT COUNT(geometry) FROM {table};".format(table=table) + row_cnt = engine.execute(sql).fetchone()[0] + assert row_cnt == 3 + + # Validate geometry type + sql = "SELECT DISTINCT GeometryType(geometry) FROM {table} ORDER BY 1;".format( + table=table + ) + res = engine.execute(sql).fetchall() + assert res[0][0].upper() == "LINESTRING" + assert res[1][0].upper() == "MULTILINESTRING" + assert res[2][0].upper() == "POINT" + + def test_write_postgis_to_different_schema(self, engine_postgis, df_nybb): + """ + Tests writing data to alternative schema. + """ + engine = engine_postgis + + table = "nybb" + schema_to_use = "test" + sql = "CREATE SCHEMA IF NOT EXISTS {schema};".format(schema=schema_to_use) + engine.execute(sql) + + write_postgis( + df_nybb, con=engine, name=table, if_exists="replace", schema=schema_to_use + ) + # Validate + sql = "SELECT * FROM {schema}.{table};".format( + schema=schema_to_use, table=table + ) + + df = read_postgis(sql, engine, geom_col="geometry") + validate_boro_df(df) + + def test_write_postgis_to_different_schema_when_table_exists( + self, engine_postgis, df_nybb + ): + """ + Tests writing data to alternative schema. + """ + engine = engine_postgis + + table = "nybb" + schema_to_use = "test" + sql = "CREATE SCHEMA IF NOT EXISTS {schema};".format(schema=schema_to_use) + engine.execute(sql) - def test_read_postgis_binary(self, df_nybb): - """Tests that geometry read as binary is accepted.""" try: - con = connect_spatialite() - except Exception: - raise pytest.skip() - else: - geom_col = df_nybb.geometry.name - create_spatialite(con, df_nybb) - sql = ( - "SELECT ogc_fid, borocode, boroname, shape_leng, shape_area, " - 'ST_AsBinary("{0}") AS "{0}" FROM nybb'.format(geom_col) + write_postgis( + df_nybb, con=engine, name=table, if_exists="fail", schema=schema_to_use ) - df = read_postgis(sql, con, geom_col=geom_col) + # Validate + sql = "SELECT * FROM {schema}.{table};".format( + schema=schema_to_use, table=table + ) + + df = read_postgis(sql, engine, geom_col="geometry") validate_boro_df(df) - finally: - if "con" in locals(): - con.close() + + # Should raise a ValueError when table exists + except ValueError: + pass + + # Try with replace flag on + write_postgis( + df_nybb, con=engine, name=table, if_exists="replace", schema=schema_to_use + ) + # Validate + sql = "SELECT * FROM {schema}.{table};".format( + schema=schema_to_use, table=table + ) + + df = read_postgis(sql, engine, geom_col="geometry") + validate_boro_df(df) + + def test_write_postgis_3D_geometries(self, engine_postgis, df_3D_geoms): + """ + Tests writing a geometries with 3 dimensions works. + """ + engine = engine_postgis + + table = "geomtype_tests" + + write_postgis(df_3D_geoms, con=engine, name=table, if_exists="replace") + + # Check that all geometries have 3 dimensions + sql = "SELECT * FROM {table};".format(table=table) + df = read_postgis(sql, engine, geom_col="geometry") + assert list(df.geometry.has_z) == [True, True, True] + + def test_row_order(self, engine_postgis, df_nybb): + """ + Tests that the row order in db table follows the order of the original frame. + """ + engine = engine_postgis + + table = "row_order_test" + correct_order = df_nybb["BoroCode"].tolist() + + write_postgis(df_nybb, con=engine, name=table, if_exists="replace") + + # Check that the row order matches + sql = "SELECT * FROM {table};".format(table=table) + df = read_postgis(sql, engine, geom_col="geometry") + assert df["BoroCode"].tolist() == correct_order + + def test_append_before_table_exists(self, engine_postgis, df_nybb): + """ + Tests that insert works with if_exists='append' when table does not exist yet. + """ + engine = engine_postgis + + table = "nybb" + # If table exists, delete it before trying to write with defaults + drop_table_if_exists(engine, table) + + write_postgis(df_nybb, con=engine, name=table, if_exists="append") + + # Check that the row order matches + sql = "SELECT * FROM {table};".format(table=table) + df = read_postgis(sql, engine, geom_col="geometry") + validate_boro_df(df) + + def test_append_with_different_crs(self, engine_postgis, df_nybb): + """ + Tests that the warning is raised if table CRS differs from frame. + """ + engine = engine_postgis + + table = "nybb" + write_postgis(df_nybb, con=engine, name=table, if_exists="replace") + + # Reproject + df_nybb2 = df_nybb.to_crs(epsg=4326) + + # Should raise error when appending + with pytest.raises(ValueError, match="CRS of the target table"): + write_postgis(df_nybb2, con=engine, name=table, if_exists="append") diff -Nru python-geopandas-0.7.0/geopandas/plotting.py python-geopandas-0.8.1/geopandas/plotting.py --- python-geopandas-0.7.0/geopandas/plotting.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/plotting.py 2020-07-15 17:54:36.000000000 +0000 @@ -5,6 +5,23 @@ import geopandas +from distutils.version import LooseVersion + + +def deprecated(new): + """Helper to provide deprecation warning.""" + + def old(*args, **kwargs): + warnings.warn( + "{} is intended for internal ".format(new.__name__[1:]) + + "use only, and will be deprecated.", + DeprecationWarning, + stacklevel=2, + ) + new(*args, **kwargs) + + return old + def _flatten_multi_geoms(geoms, prefix="Multi"): """ @@ -17,7 +34,6 @@ Returns ------- - components : list of geometry component_index : index array @@ -40,7 +56,37 @@ return components, np.array(component_index) -def plot_polygon_collection( +def _expand_kwargs(kwargs, multiindex): + """ + Most arguments to the plot functions must be a (single) value, or a sequence + of values. This function checks each key-value pair in 'kwargs' and expands + it (in place) to the correct length/formats with help of 'multiindex', unless + the value appears to already be a valid (single) value for the key. + """ + from matplotlib.colors import is_color_like + from typing import Iterable + + for att, value in kwargs.items(): + if "color" in att: # color(s), edgecolor(s), facecolor(s) + if is_color_like(value): + continue + elif "linestyle" in att: # linestyle(s) + # A single linestyle can be 2-tuple of a number and an iterable. + if ( + isinstance(value, tuple) + and len(value) == 2 + and isinstance(value[1], Iterable) + ): + continue + elif att in ["marker", "alpha"]: + # For these attributes, only a single value is allowed, so never expand. + continue + + if pd.api.types.is_list_like(value): + kwargs[att] = np.take(value, multiindex, axis=0) + + +def _plot_polygon_collection( ax, geoms, values=None, color=None, cmap=None, vmin=None, vmax=None, **kwargs ): """ @@ -48,32 +94,25 @@ Parameters ---------- - ax : matplotlib.axes.Axes where shapes will be plotted - geoms : a sequence of `N` Polygons and/or MultiPolygons (can be mixed) values : a sequence of `N` values, optional Values will be mapped to colors using vmin/vmax/cmap. They should have 1:1 correspondence with the geometries (not their components). Otherwise follows `color` / `facecolor` kwargs. - edgecolor : single color or sequence of `N` colors Color for the edge of the polygons - facecolor : single color or sequence of `N` colors Color to fill the polygons. Cannot be used together with `values`. - color : single color or sequence of `N` colors Sets both `edgecolor` and `facecolor` - **kwargs Additional keyword arguments passed to the collection Returns ------- - collection : matplotlib.collections.Collection that was plotted """ @@ -86,36 +125,23 @@ "'pip install descartes'." ) from matplotlib.collections import PatchCollection - from matplotlib.colors import is_color_like geoms, multiindex = _flatten_multi_geoms(geoms) if values is not None: values = np.take(values, multiindex, axis=0) # PatchCollection does not accept some kwargs. - if "markersize" in kwargs: - del kwargs["markersize"] + kwargs = { + att: value + for att, value in kwargs.items() + if att not in ["markersize", "marker"] + } + + # Add to kwargs for easier checking below. if color is not None: - if is_color_like(color): - kwargs["color"] = color - elif pd.api.types.is_list_like(color): - kwargs["color"] = np.take(color, multiindex, axis=0) - else: - raise TypeError( - "Color attribute has to be a single color or sequence of colors." - ) + kwargs["color"] = color - else: - for att in ["facecolor", "edgecolor"]: - if att in kwargs: - if not is_color_like(kwargs[att]): - if pd.api.types.is_list_like(kwargs[att]): - kwargs[att] = np.take(kwargs[att], multiindex, axis=0) - elif kwargs[att] is not None: - raise TypeError( - "Color attribute has to be a single color or sequence " - "of colors." - ) + _expand_kwargs(kwargs, multiindex) collection = PatchCollection([PolygonPatch(poly) for poly in geoms], **kwargs) @@ -130,7 +156,10 @@ return collection -def plot_linestring_collection( +plot_polygon_collection = deprecated(_plot_polygon_collection) + + +def _plot_linestring_collection( ax, geoms, values=None, color=None, cmap=None, vmin=None, vmax=None, **kwargs ): """ @@ -138,47 +167,38 @@ Parameters ---------- - ax : matplotlib.axes.Axes where shapes will be plotted - geoms : a sequence of `N` LineStrings and/or MultiLineStrings (can be mixed) - values : a sequence of `N` values, optional Values will be mapped to colors using vmin/vmax/cmap. They should have 1:1 correspondence with the geometries (not their components). - color : single color or sequence of `N` colors Cannot be used together with `values`. Returns ------- - collection : matplotlib.collections.Collection that was plotted - """ from matplotlib.collections import LineCollection - from matplotlib.colors import is_color_like geoms, multiindex = _flatten_multi_geoms(geoms) if values is not None: values = np.take(values, multiindex, axis=0) # LineCollection does not accept some kwargs. - if "markersize" in kwargs: - del kwargs["markersize"] + kwargs = { + att: value + for att, value in kwargs.items() + if att not in ["markersize", "marker"] + } - # color=None gives black instead of default color cycle + # Add to kwargs for easier checking below. if color is not None: - if is_color_like(color): - kwargs["color"] = color - elif pd.api.types.is_list_like(color): - kwargs["color"] = np.take(color, multiindex, axis=0) - else: - raise TypeError( - "Color attribute has to be a single color or sequence of colors." - ) + kwargs["color"] = color + + _expand_kwargs(kwargs, multiindex) segments = [np.array(linestring)[:, :2] for linestring in geoms] collection = LineCollection(segments, **kwargs) @@ -194,7 +214,10 @@ return collection -def plot_point_collection( +plot_linestring_collection = deprecated(_plot_linestring_collection) + + +def _plot_point_collection( ax, geoms, values=None, @@ -227,8 +250,6 @@ ------- collection : matplotlib.collections.Collection that was plotted """ - from matplotlib.colors import is_color_like - if values is not None and color is not None: raise ValueError("Can only specify one of 'values' and 'color' kwargs") @@ -245,26 +266,27 @@ if markersize is not None: kwargs["s"] = markersize + # Add to kwargs for easier checking below. if color is not None: - if not is_color_like(color): - if pd.api.types.is_list_like(color): - color = np.take(color, multiindex, axis=0) - else: - raise TypeError( - "Color attribute has to be a single color or sequence of colors." - ) + kwargs["color"] = color + if marker is not None: + kwargs["marker"] = marker + _expand_kwargs(kwargs, multiindex) if "norm" not in kwargs: - collection = ax.scatter( - x, y, color=color, vmin=vmin, vmax=vmax, cmap=cmap, marker=marker, **kwargs - ) + collection = ax.scatter(x, y, vmin=vmin, vmax=vmax, cmap=cmap, **kwargs) else: - collection = ax.scatter(x, y, color=color, cmap=cmap, marker=marker, **kwargs) + collection = ax.scatter(x, y, cmap=cmap, **kwargs) return collection -def plot_series(s, cmap=None, color=None, ax=None, figsize=None, **style_kwds): +plot_point_collection = deprecated(_plot_point_collection) + + +def plot_series( + s, cmap=None, color=None, ax=None, figsize=None, aspect="auto", **style_kwds +): """ Plot a GeoSeries. @@ -291,6 +313,14 @@ figsize : pair of floats (default None) Size of the resulting matplotlib.figure.Figure. If the argument ax is given explicitly, figsize is ignored. + aspect : 'auto', 'equal' or float (default 'auto') + Set aspect of axis. If 'auto', the default aspect for map plots is 'equal'; if + however data are not projected (coordinates are long/lat), the aspect is by + default set to 1/cos(s_y * pi/180) with s_y the y coordinate of the middle of + the GeoSeries (the mean of the y range of bounding box) so that a long/lat + square appears square in the middle of the plot. This implies an + Equirectangular projection. It can also be set manually (float) as the ratio + of y-unit to x-unit. **style_kwds : dict Color options to be passed on to the actual plot function, such as ``edgecolor``, ``facecolor``, ``linewidth``, ``markersize``, @@ -326,7 +356,18 @@ if ax is None: fig, ax = plt.subplots(figsize=figsize) - ax.set_aspect("equal") + + if aspect == "auto": + if s.crs and s.crs.is_geographic: + bounds = s.total_bounds + y_coord = np.mean([bounds[1], bounds[3]]) + ax.set_aspect(1 / np.cos(y_coord * np.pi / 180)) + # formula ported from R package sp + # https://github.com/edzer/sp/blob/master/R/mapasp.R + else: + ax.set_aspect("equal") + else: + ax.set_aspect(aspect) if s.empty: warnings.warn( @@ -369,7 +410,7 @@ facecolor = color values_ = values[poly_idx] if cmap else None - plot_polygon_collection( + _plot_polygon_collection( ax, polys, values_, facecolor=facecolor, cmap=cmap, **style_kwds ) @@ -377,7 +418,7 @@ lines = expl_series[line_idx] if not lines.empty: values_ = values[line_idx] if cmap else None - plot_linestring_collection( + _plot_linestring_collection( ax, lines, values_, color=color, cmap=cmap, **style_kwds ) @@ -385,7 +426,9 @@ points = expl_series[point_idx] if not points.empty: values_ = values[point_idx] if cmap else None - plot_point_collection(ax, points, values_, color=color, cmap=cmap, **style_kwds) + _plot_point_collection( + ax, points, values_, color=color, cmap=cmap, **style_kwds + ) plt.draw() return ax @@ -407,8 +450,10 @@ markersize=None, figsize=None, legend_kwds=None, + categories=None, classification_kwds=None, missing_kwds=None, + aspect="auto", **style_kwds ): """ @@ -472,6 +517,17 @@ legend_kwds : dict (default None) Keyword arguments to pass to matplotlib.pyplot.legend() or matplotlib.pyplot.colorbar(). + Additional accepted keywords when `scheme` is specified: + + fmt : string + A formatting specification for the bin edges of the classes in the + legend. For example, to have no decimals: ``{"fmt": "{:.0f}"}``. + labels : list-like + A list of legend labels to override the auto-generated labels. + Needs to have the same number of elements as the number of + classes (`k`). + categories : list-like + Ordered list-like object of categories to be used for categorical plot. classification_kwds : dict (default None) Keyword arguments to pass to mapclassify missing_kwds : dict (default None) @@ -479,9 +535,17 @@ to be passed on to geometries with missing values in addition to or overwriting other style kwds. If None, geometries with missing values are not plotted. + aspect : 'auto', 'equal' or float (default 'auto') + Set aspect of axis. If 'auto', the default aspect for map plots is 'equal'; if + however data are not projected (coordinates are long/lat), the aspect is by + default set to 1/cos(df_y * pi/180) with df_y the y coordinate of the middle of + the GeoDataFrame (the mean of the y range of bounding box) so that a long/lat + square appears square in the middle of the plot. This implies an + Equirectangular projection. It can also be set manually (float) as the ratio + of y-unit to x-unit. **style_kwds : dict - Color options to be passed on to the actual plot function, such + Style options to be passed on to the actual plot function, such as ``edgecolor``, ``facecolor``, ``linewidth``, ``markersize``, ``alpha``. @@ -523,7 +587,18 @@ if cax is not None: raise ValueError("'ax' can not be None if 'cax' is not.") fig, ax = plt.subplots(figsize=figsize) - ax.set_aspect("equal") + + if aspect == "auto": + if df.crs and df.crs.is_geographic: + bounds = df.total_bounds + y_coord = np.mean([bounds[1], bounds[3]]) + ax.set_aspect(1 / np.cos(y_coord * np.pi / 180)) + # formula ported from R package sp + # https://github.com/edzer/sp/blob/master/R/mapasp.R + else: + ax.set_aspect("equal") + else: + ax.set_aspect(aspect) if df.empty: warnings.warn( @@ -544,6 +619,7 @@ ax=ax, figsize=figsize, markersize=markersize, + aspect=aspect, **style_kwds ) @@ -554,23 +630,40 @@ "The dataframe and given column have different number of rows." ) else: - values = np.asarray(column) + values = column else: - values = np.asarray(df[column]) + values = df[column] - if values.dtype is np.dtype("O"): + if pd.api.types.is_categorical_dtype(values.dtype): + if categories is not None: + raise ValueError( + "Cannot specify 'categories' when column has categorical dtype" + ) + categorical = True + elif values.dtype is np.dtype("O") or categories: categorical = True - nan_idx = pd.isna(values) + nan_idx = np.asarray(pd.isna(values), dtype="bool") # Define `values` as a Series if categorical: if cmap is None: cmap = "tab10" - categories = list(set(values[~nan_idx])) - categories.sort() - valuemap = dict((k, v) for (v, k) in enumerate(categories)) - values = np.array([valuemap[k] for k in values[~nan_idx]]) + + cat = pd.Categorical(values, categories=categories) + categories = list(cat.categories) + + # values missing in the Categorical but not in original values + missing = list(np.unique(values[~nan_idx & cat.isna()])) + if missing: + raise ValueError( + "Column contains values not listed in categories. " + "Missing categories: {}.".format(missing) + ) + + values = cat.codes[~nan_idx] + vmin = 0 if vmin is None else vmin + vmax = len(categories) - 1 if vmax is None else vmax if scheme is not None: if classification_kwds is None: @@ -581,11 +674,21 @@ binning = _mapclassify_choro(values[~nan_idx], scheme, **classification_kwds) # set categorical to True for creating the legend categorical = True - binedges = [values[~nan_idx].min()] + binning.bins.tolist() - categories = [ - "{0:.2f} - {1:.2f}".format(binedges[i], binedges[i + 1]) - for i in range(len(binedges) - 1) - ] + if legend_kwds is not None and "labels" in legend_kwds: + if len(legend_kwds["labels"]) != binning.k: + raise ValueError( + "Number of labels must match number of bins, " + "received {} labels for {} bins".format( + len(legend_kwds["labels"]), binning.k + ) + ) + else: + categories = list(legend_kwds.pop("labels")) + else: + fmt = "{:.2f}" + if legend_kwds is not None and "fmt" in legend_kwds: + fmt = legend_kwds.pop("fmt") + categories = binning.get_legend_classes(fmt) values = np.array(binning.yb) # fill values with placeholder where were NaNs originally to map them properly @@ -616,7 +719,7 @@ polys = expl_series[poly_idx & np.invert(nan_idx)] subset = values[poly_idx & np.invert(nan_idx)] if not polys.empty: - plot_polygon_collection( + _plot_polygon_collection( ax, polys, subset, vmin=mn, vmax=mx, cmap=cmap, **style_kwds ) @@ -624,7 +727,7 @@ lines = expl_series[line_idx & np.invert(nan_idx)] subset = values[line_idx & np.invert(nan_idx)] if not lines.empty: - plot_linestring_collection( + _plot_linestring_collection( ax, lines, subset, vmin=mn, vmax=mx, cmap=cmap, **style_kwds ) @@ -635,7 +738,7 @@ if isinstance(markersize, np.ndarray): markersize = np.take(markersize, multiindex, axis=0) markersize = markersize[point_idx & np.invert(nan_idx)] - plot_point_collection( + _plot_point_collection( ax, points, subset, @@ -660,6 +763,8 @@ if legend_kwds is None: legend_kwds = {} + if "fmt" in legend_kwds: + legend_kwds.pop("fmt") from matplotlib.lines import Line2D from matplotlib.colors import Normalize @@ -746,19 +851,21 @@ binning Binning objects that holds the Series with values replaced with class identifier and the bins. - """ try: import mapclassify.classifiers as classifiers + except ImportError: - try: - import pysal.viz.mapclassify.classifiers as classifiers - except ImportError: - raise ImportError( - "The 'mapclassify' or 'pysal' package is required to use the" - " 'scheme' keyword" - ) + raise ImportError( + "The 'mapclassify' >= 2.2.0 package is required to use the 'scheme' keyword" + ) + from mapclassify import __version__ as mc_version + if mc_version < LooseVersion("2.2.0"): + raise ImportError( + "The 'mapclassify' >= 2.2.0 package is required to " + "use the 'scheme' keyword" + ) schemes = {} for classifier in classifiers.CLASSIFIERS: schemes[classifier.lower()] = getattr(classifiers, classifier) @@ -803,15 +910,13 @@ ) if classification_kwds["k"] is not None: - try: - from inspect import getfullargspec as getspec - except ImportError: - from inspect import getargspec as getspec + from inspect import getfullargspec as getspec + spec = getspec(scheme_class.__init__) if "k" not in spec.args: del classification_kwds["k"] try: - binning = scheme_class(values, **classification_kwds) + binning = scheme_class(np.asarray(values), **classification_kwds) except TypeError: raise TypeError("Invalid keyword argument for %r " % scheme) return binning diff -Nru python-geopandas-0.7.0/geopandas/sindex.py python-geopandas-0.8.1/geopandas/sindex.py --- python-geopandas-0.7.0/geopandas/sindex.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/sindex.py 2020-07-15 17:54:36.000000000 +0000 @@ -1,25 +1,481 @@ -from geopandas import base +from collections import namedtuple +from warnings import warn -if base.HAS_SINDEX: - from rtree.index import Index as RTreeIndex +from shapely.geometry.base import BaseGeometry +import pandas as pd +import numpy as np +from . import _compat as compat -class SpatialIndex(RTreeIndex): + +VALID_QUERY_PREDICATES = { + None, + "intersects", + "within", + "contains", + "overlaps", + "crosses", + "touches", +} + + +def has_sindex(): + """Dynamically checks for ability to generate spatial index. """ - A simple wrapper around rtree's RTree Index + try: + get_sindex_class() + return True + except ImportError: + return False + + +def get_sindex_class(): + """Dynamically chooses a spatial indexing backend. + + Required to comply with _compat.USE_PYGEOS. + The selection order goes PyGEOS > RTree > Error. """ + if compat.USE_PYGEOS: + return PyGEOSSTRTreeIndex + if compat.HAS_RTREE: + return RTreeIndex + raise ImportError( + "Spatial indexes require either `rtree` or `pygeos`. " + "See installation instructions at https://geopandas.org/install.html" + ) + + +if compat.HAS_RTREE: + + import rtree.index # noqa + from rtree.core import RTreeError # noqa + from shapely.prepared import prep # noqa + + class SpatialIndex(rtree.index.Index): + """Original rtree wrapper, kept for backwards compatibility. + """ + + def __init__(self, *args): + super().__init__(self, *args) + + @property + def size(self): + return len(self.leaves()[0][1]) + + @property + def is_empty(self): + if len(self.leaves()) > 1: + return False + return self.size < 1 + + class RTreeIndex(rtree.index.Index): + """A simple wrapper around rtree's RTree Index + + Parameters + ---------- + geometry : GeoSeries + GeoSeries from which to build the spatial index. + """ + + # set of valid predicates for this spatial index + # by default, the global set + valid_query_predicates = VALID_QUERY_PREDICATES + + def __init__(self, geometry): + stream = ( + (i, item.bounds, idx) + for i, (idx, item) in enumerate(geometry.iteritems()) + if pd.notnull(item) and not item.is_empty + ) + try: + super().__init__(stream) + except RTreeError: + # What we really want here is an empty generator error, or + # for the bulk loader to log that the generator was empty + # and move on. + # See https://github.com/Toblerity/rtree/issues/20. + super().__init__() + + # store reference to geometries for predicate queries + self.geometries = geometry.geometry.values + # create a prepared geometry cache + self._prepared_geometries = np.array( + [None] * self.geometries.size, dtype=object + ) + + def query(self, geometry, predicate=None, sort=False): + """Compatibility layer for pygeos.query. + + This is not a vectorized function, if speed is important, + please use PyGEOS. + + Parameters + ---------- + geometry : shapely geometry + A single shapely geometry to query against the spatial index. + predicate : {None, 'intersects', 'within', 'contains', \ +'overlaps', 'crosses', 'touches'}, optional + If predicate is provided, the input geometry is + tested using the predicate function against each item + in the tree whose extent intersects the envelope of the + input geometry: predicate(input_geometry, tree_geometry). + If possible, prepared geometries are used to help + speed up the predicate operation. + sort : bool, default False + If True, the results will be sorted in ascending order. + If False, results are often sorted but there is no guarantee. + + Returns + ------- + matches : ndarray of shape (n_results, ) + Integer indices for matching geometries from the spatial index. + """ + + # handle invalid predicates + if predicate not in self.valid_query_predicates: + raise ValueError( + "Got `predicate` = `{}`, `predicate` must be one of {}".format( + predicate, self.valid_query_predicates + ) + ) + + # handle empty / invalid geometries + if geometry is None: + # return an empty integer array, similar to pygeys.STRtree.query. + return np.array([], dtype=np.intp) + + if not isinstance(geometry, BaseGeometry): + raise TypeError( + "Got `geometry` of type `{}`, `geometry` must be ".format( + type(geometry) + ) + + "a shapely geometry." + ) + + if geometry.is_empty: + return np.array([], dtype=np.intp) + + # query tree + bounds = geometry.bounds # rtree operates on bounds + tree_idx = list(self.intersection(bounds, objects=False)) + + if not tree_idx: + return np.array([], dtype=np.intp) + + # Check predicate + # This is checked as input_geometry.predicate(tree_geometry) + # When possible, we use prepared geometries. + # Prepared geometries only support "intersects" and "contains" + # For the special case of "within", we are able to flip the + # comparison and check if tree_geometry.contains(input_geometry) + # to still take advantage of prepared geometries. + if predicate == "within": + # To use prepared geometries for within, + # we compare tree_geom.contains(input_geom) + # Since we are preparing the tree geometries, + # we cache them for multiple comparisons. + res = [] + for index_in_tree in tree_idx: + if self._prepared_geometries[index_in_tree] is None: + # if not already prepared, prepare and cache + self._prepared_geometries[index_in_tree] = prep( + self.geometries[index_in_tree] + ) + if self._prepared_geometries[index_in_tree].contains(geometry): + res.append(index_in_tree) + tree_idx = res + elif predicate is not None: + # For the remaining predicates, + # we compare input_geom.predicate(tree_geom) + if predicate in ("contains", "intersects"): + # prepare this input geometry + geometry = prep(geometry) + tree_idx = [ + index_in_tree + for index_in_tree in tree_idx + if getattr(geometry, predicate)(self.geometries[index_in_tree]) + ] + + # sort if requested + if sort: + # sorted + return np.sort(np.array(tree_idx, dtype=np.intp)) + + # unsorted + return np.array(tree_idx, dtype=np.intp) + + def query_bulk(self, geometry, predicate=None, sort=False): + """Compatibility layer for pygeos.query_bulk. + + Iterates over `geometry` and queries index. + This operation is not vectorized and may be slow. + Use PyGEOS with `query_bulk` for speed. + + Parameters + ---------- + geometry : {GeoSeries, GeometryArray, numpy.array of PyGEOS geometries} + Accepts GeoPandas geometry iterables (GeoSeries, GeometryArray) + or a numpy array of PyGEOS geometries. + predicate : {None, 'intersects', 'within', 'contains', 'overlaps', \ +'crosses', 'touches'}, optional + If predicate is provided, the input geometries are tested using + the predicate function against each item in the tree whose extent + intersects the envelope of the each input geometry: + predicate(input_geometry, tree_geometry). If possible, prepared + geometries are used to help speed up the predicate operation. + sort : bool, default False + If True, results sorted lexicographically using + geometry's indexes as the primary key and the sindex's indexes as the + secondary key. If False, no additional sorting is applied. + + Returns + ------- + ndarray with shape (2, n) + The first subarray contains input geometry integer indexes. + The second subarray contains tree geometry integer indexes. + """ + # Iterates over geometry, applying func. + tree_index = [] + input_geometry_index = [] + + for i, geo in enumerate(geometry): + res = self.query(geo, predicate=predicate, sort=sort) + tree_index.extend(res) + input_geometry_index.extend([i] * len(res)) + return np.vstack([input_geometry_index, tree_index]) + + def intersection(self, coordinates, objects=False): + """Find tree geometries that intersect the input coordinates. + + Parameters + ---------- + coordinates : sequence or array + Sequence of the form (min_x, min_y, max_x, max_y) + to query a rectangle or (x, y) to query a point. + objects : boolean, default False + If True, return the label based indexes. If False, integer indexes + are returned. + """ + if objects: + warn( + "`objects` is deprecated and will be removed in a future version. " + "Instead, use `iloc` to index your GeoSeries/GeoDataFrame using " + "integer indexes returned by `intersection`.", + FutureWarning, + ) + return super().intersection(coordinates, objects) + + @property + def size(self): + return len(self.leaves()[0][1]) + + @property + def is_empty(self): + return self.size == 0 + + def __len__(self): + return self.size + + +if compat.HAS_PYGEOS: + + from . import geoseries # noqa + from .array import GeometryArray, _shapely_to_geom # noqa + import pygeos # noqa + + class PyGEOSSTRTreeIndex(pygeos.STRtree): + """A simple wrapper around pygeos's STRTree. + + + Parameters + ---------- + geometry : GeoSeries + GeoSeries from which to build the spatial index. + """ + + # helper for loc/label based indexing in `intersection` method + with_objects = namedtuple("with_objects", "object id") + + # set of valid predicates for this spatial index + # by default, the global set + valid_query_predicates = VALID_QUERY_PREDICATES + + def __init__(self, geometry): + # for compatibility with old RTree implementation, store ids/indexes + original_indexes = geometry.index + # set empty geometries to None to avoid segfault on GEOS <= 3.6 + # see: + # https://github.com/pygeos/pygeos/issues/146 + # https://github.com/pygeos/pygeos/issues/147 + non_empty = geometry.values.data.copy() + non_empty[pygeos.is_empty(non_empty)] = None + # set empty geometries to None to mantain indexing + self.objects = self.ids = original_indexes + super().__init__(non_empty) + # store geometries, including empty geometries for user access + self.geometries = geometry.values.data.copy() + + def query(self, geometry, predicate=None, sort=False): + """Wrapper for pygeos.query. + + This also ensures a deterministic (sorted) order for the results. + + Parameters + ---------- + geometry : single PyGEOS geometry + predicate : {None, 'intersects', 'within', 'contains', \ +'overlaps', 'crosses', 'touches'}, optional + If predicate is provided, the input geometry is tested + using the predicate function against each item in the + tree whose extent intersects the envelope of the input + geometry: predicate(input_geometry, tree_geometry). + sort : bool, default False + If True, the results will be sorted in ascending order. + If False, results are often sorted but there is no guarantee. + + Returns + ------- + matches : ndarray of shape (n_results, ) + Integer indices for matching geometries from the spatial index. + + See also + -------- + See PyGEOS.strtree documentation for more information. + """ + + if predicate not in self.valid_query_predicates: + raise ValueError( + "Got `predicate` = `{}`; ".format(predicate) + + "`predicate` must be one of {}".format( + self.valid_query_predicates + ) + ) + + if isinstance(geometry, BaseGeometry): + geometry = _shapely_to_geom(geometry) + + matches = super().query(geometry=geometry, predicate=predicate) + + if sort: + return np.sort(matches) + + return matches + + def query_bulk(self, geometry, predicate=None, sort=False): + """Wrapper to expose underlaying pygeos objects to pygeos.query_bulk. + + This also allows a deterministic (sorted) order for the results. + + + Parameters + ---------- + geometry : {GeoSeries, GeometryArray, numpy.array of PyGEOS geometries} + Accepts GeoPandas geometry iterables (GeoSeries, GeometryArray) + or a numpy array of PyGEOS geometries. + predicate : {None, 'intersects', 'within', 'contains', \ +'overlaps', 'crosses', 'touches'}, optional + If predicate is provided, the input geometry is tested + using the predicate function against each item in the + index whose extent intersects the envelope of the input geometry: + predicate(input_geometry, tree_geometry). + sort : bool, default False + If True, results sorted lexicographically using + geometry's indexes as the primary key and the sindex's indexes as the + secondary key. If False, no additional sorting is applied. + + Returns + ------- + ndarray with shape (2, n) + The first subarray contains input geometry integer indexes. + The second subarray contains tree geometry integer indexes. + + See also + -------- + See PyGEOS.strtree documentation for more information. + """ + + if predicate not in self.valid_query_predicates: + raise ValueError( + "Got `predicate` = `{}`, `predicate` must be one of {}".format( + predicate, self.valid_query_predicates + ) + ) + if isinstance(geometry, geoseries.GeoSeries): + geometry = geometry.values.data + elif isinstance(geometry, GeometryArray): + geometry = geometry.data + elif not isinstance(geometry, np.ndarray): + geometry = np.asarray(geometry) + + res = super().query_bulk(geometry, predicate) + + if sort: + # sort by first array (geometry) and then second (tree) + geo_res, tree_res = res + indexing = np.lexsort((tree_res, geo_res)) + return np.vstack((geo_res[indexing], tree_res[indexing])) + + return res + + def intersection(self, coordinates, objects=False): + """Wrapper for pygeos.query that uses the RTree API. + + Parameters + ---------- + coordinates : sequence or array + Sequence of the form (min_x, min_y, max_x, max_y) + to query a rectangle or (x, y) to query a point. + objects : boolean, default False + If True, return the label based indexes. If False, integer indexes + are returned. + """ + if objects: + warn( + "`objects` is deprecated and will be removed in a future version. " + "Instead, use `iloc` to index your GeoSeries/GeoDataFrame using " + "integer indexes returned by `intersection`.", + FutureWarning, + ) + + # convert bounds to geometry + # the old API uses tuples of bound, but pygeos uses geometries + try: + iter(coordinates) + except TypeError: + # likely not an iterable + # this is a check that rtree does, we mimic it + # to ensure a useful failure message + raise TypeError( + "Invalid coordinates, must be iterable in format " + "(minx, miny, maxx, maxy) (for bounds) or (x, y) (for points). " + "Got `coordinates` = {}.".format(coordinates) + ) + + # need to convert tuple of bounds to a geometry object + if len(coordinates) == 4: + indexes = super().query(pygeos.box(*coordinates)) + elif len(coordinates) == 2: + indexes = super().query(pygeos.points(*coordinates)) + else: + raise TypeError( + "Invalid coordinates, must be iterable in format " + "(minx, miny, maxx, maxy) (for bounds) or (x, y) (for points). " + "Got `coordinates` = {}.".format(coordinates) + ) + + if objects: + objs = self.objects[indexes].values + ids = self.ids[indexes] + return [ + self.with_objects(id=id, object=obj) for id, obj in zip(ids, objs) + ] + else: + return indexes + + @property + def size(self): + return len(self) - def __init__(self, *args): - if not base.HAS_SINDEX: - raise ImportError("SpatialIndex needs `rtree`") - RTreeIndex.__init__(self, *args) - - @property - def size(self): - return len(self.leaves()[0][1]) - - @property - def is_empty(self): - if len(self.leaves()) > 1: - return False - return self.size < 1 + @property + def is_empty(self): + return len(self) == 0 diff -Nru python-geopandas-0.7.0/geopandas/testing.py python-geopandas-0.8.1/geopandas/testing.py --- python-geopandas-0.7.0/geopandas/testing.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/testing.py 2020-07-15 17:54:36.000000000 +0000 @@ -61,7 +61,7 @@ def assert_geoseries_equal( left, right, - check_dtype=False, + check_dtype=True, check_index_type=False, check_series_type=True, check_less_precise=False, @@ -92,16 +92,14 @@ """ assert len(left) == len(right), "%d != %d" % (len(left), len(right)) - msg = "dtype should be a GeometryDtype, got {0}" - assert isinstance(left.dtype, GeometryDtype), msg.format(left.dtype) - assert isinstance(right.dtype, GeometryDtype), msg.format(left.dtype) + if check_dtype: + msg = "dtype should be a GeometryDtype, got {0}" + assert isinstance(left.dtype, GeometryDtype), msg.format(left.dtype) + assert isinstance(right.dtype, GeometryDtype), msg.format(left.dtype) if check_index_type: assert isinstance(left.index, type(right.index)) - if check_dtype: - assert left.dtype == right.dtype, "dtype: %s != %s" % (left.dtype, right.dtype) - if check_series_type: assert isinstance(left, GeoSeries) assert isinstance(left, type(right)) @@ -124,7 +122,7 @@ if not check_crs: with warnings.catch_warnings(): - warnings.filterwarnings("ignore", "GeoSeries crs mismatch", UserWarning) + warnings.filterwarnings("ignore", "CRS mismatch", UserWarning) if check_less_precise: assert geom_almost_equals(left, right) else: @@ -214,14 +212,16 @@ ) # geometry comparison - assert_geoseries_equal( - left.geometry, - right.geometry, - check_dtype=check_dtype, - check_less_precise=check_less_precise, - check_geom_type=check_geom_type, - check_crs=False, - ) + for col, dtype in left.dtypes.iteritems(): + if isinstance(dtype, GeometryDtype): + assert_geoseries_equal( + left[col], + right[col], + check_dtype=check_dtype, + check_less_precise=check_less_precise, + check_geom_type=check_geom_type, + check_crs=check_crs, + ) # drop geometries and check remaining columns left2 = left.drop([left._geometry_column_name], axis=1) diff -Nru python-geopandas-0.7.0/geopandas/tests/test_api.py python-geopandas-0.8.1/geopandas/tests/test_api.py --- python-geopandas-0.7.0/geopandas/tests/test_api.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/test_api.py 2020-07-15 17:54:36.000000000 +0000 @@ -18,6 +18,7 @@ "sqlalchemy", "psycopg2", "geopy", + "geoalchemy2", } if PANDAS_GE_10: # pandas > 0.25 stopped importing matplotlib by default diff -Nru python-geopandas-0.7.0/geopandas/tests/test_array.py python-geopandas-0.8.1/geopandas/tests/test_array.py --- python-geopandas-0.7.0/geopandas/tests/test_array.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/test_array.py 2020-07-15 17:54:36.000000000 +0000 @@ -20,7 +20,10 @@ points_from_xy, to_wkb, to_wkt, + _check_crs, + _crs_mismatch_warn, ) +import geopandas._compat as compat import pytest @@ -70,15 +73,19 @@ gsz = [shapely.geometry.Point(x, x, x) for x in range(10)] geometry1 = geopandas.points_from_xy(df["x"], df["y"]) geometry2 = geopandas.points_from_xy(df["x"], df["y"], df["z"]) - assert geometry1 == gs - assert geometry2 == gsz + assert isinstance(geometry1, GeometryArray) + assert isinstance(geometry2, GeometryArray) + assert list(geometry1) == gs + assert list(geometry2) == gsz # using Series or numpy arrays or lists for s in [pd.Series(range(10)), np.arange(10), list(range(10))]: geometry1 = geopandas.points_from_xy(s, s) geometry2 = geopandas.points_from_xy(s, s, s) - assert geometry1 == gs - assert geometry2 == gsz + assert isinstance(geometry1, GeometryArray) + assert isinstance(geometry2, GeometryArray) + assert list(geometry1) == gs + assert list(geometry2) == gsz # using different lengths should throw error arr_10 = np.arange(10) @@ -129,10 +136,15 @@ assert all(v.equals(t) for v, t in zip(res, points_no_missing)) # missing values - L_wkb.extend([b"", None]) + # TODO(pygeos) does not support empty strings + if compat.USE_PYGEOS: + L_wkb.extend([None]) + else: + L_wkb.extend([b"", None]) res = from_wkb(L_wkb) assert res[-1] is None - assert res[-2] is None + if not compat.USE_PYGEOS: + assert res[-2] is None # single MultiPolygon multi_poly = shapely.geometry.MultiPolygon( @@ -149,6 +161,11 @@ assert isinstance(res, np.ndarray) np.testing.assert_array_equal(res, exp) + res = to_wkb(P, hex=True) + exp = np.array([p.wkb_hex for p in points_no_missing], dtype=object) + assert isinstance(res, np.ndarray) + np.testing.assert_array_equal(res, exp) + # missing values a = from_shapely([None, points_no_missing[0]]) res = to_wkb(a) @@ -182,10 +199,15 @@ assert all(v.almost_equals(t) for v, t in zip(res, points_no_missing)) # missing values - L_wkt.extend([f(""), None]) + # TODO(pygeos) does not support empty strings + if compat.USE_PYGEOS: + L_wkt.extend([None]) + else: + L_wkt.extend([f(""), None]) res = from_wkt(L_wkt) assert res[-1] is None - assert res[-2] is None + if not compat.USE_PYGEOS: + assert res[-2] is None # single MultiPolygon multi_poly = shapely.geometry.MultiPolygon( @@ -197,7 +219,7 @@ def test_to_wkt(): P = from_shapely(points_no_missing) - res = to_wkt(P) + res = to_wkt(P, rounding_precision=-1) exp = np.array([p.wkt for p in points_no_missing], dtype=object) assert isinstance(res, np.ndarray) np.testing.assert_array_equal(res, exp) @@ -215,13 +237,13 @@ ("covers", ()), ("crosses", ()), ("disjoint", ()), - ("equals", ()), + ("geom_equals", ()), ("intersects", ()), ("overlaps", ()), ("touches", ()), ("within", ()), - ("equals_exact", (0.1,)), - ("almost_equals", (3,)), + ("geom_equals_exact", (0.1,)), + ("geom_almost_equals", (3,)), ], ) def test_predicates_vector_scalar(attr, args): @@ -236,7 +258,9 @@ assert result.dtype == bool expected = [ - getattr(tri, attr)(other, *args) if tri is not None else na_value + getattr(tri, attr if "geom" not in attr else attr[5:])(other, *args) + if tri is not None + else na_value for tri in triangles ] @@ -252,13 +276,13 @@ ("covers", ()), ("crosses", ()), ("disjoint", ()), - ("equals", ()), + ("geom_equals", ()), ("intersects", ()), ("overlaps", ()), ("touches", ()), ("within", ()), - ("equals_exact", (0.1,)), - ("almost_equals", (3,)), + ("geom_equals_exact", (0.1,)), + ("geom_almost_equals", (3,)), ], ) def test_predicates_vector_vector(attr, args): @@ -294,12 +318,27 @@ elif a.is_empty or b.is_empty: expected.append(empty_value) else: - expected.append(getattr(a, attr)(b, *args)) + expected.append( + getattr(a, attr if "geom" not in attr else attr[5:])(b, *args) + ) assert result.tolist() == expected @pytest.mark.parametrize( + "attr,args", [("equals_exact", (0.1,)), ("almost_equals", (3,))], +) +def test_equals_deprecation(attr, args): + point = points[0] + tri = triangles[0] + + for other in [point, tri, shapely.geometry.Polygon()]: + with pytest.warns(FutureWarning): + result = getattr(T, attr)(other, *args) + assert result.tolist() == getattr(T, "geom_" + attr)(other, *args).tolist() + + +@pytest.mark.parametrize( "attr", [ "boundary", @@ -314,9 +353,11 @@ na_value = None if attr == "boundary": - # boundary raises for empty geometry - with pytest.raises(Exception): - T.boundary + # pygeos returns None for empty geometries + if not compat.USE_PYGEOS: + # boundary raises for empty geometry + with pytest.raises(Exception): + T.boundary values = triangle_no_missing + [None] A = from_shapely(values) @@ -325,7 +366,17 @@ A = T result = getattr(A, attr) - expected = [getattr(t, attr) if t is not None else na_value for t in values] + if attr == "exterior" and compat.USE_PYGEOS: + # TODO(pygeos) + # empty Polygon() has an exterior with shapely > 1.7, which gives + # empty LinearRing instead of None, + # but conversion to pygeos still results in empty GeometryCollection + expected = [ + getattr(t, attr) if t is not None and not t.is_empty else na_value + for t in values + ] + else: + expected = [getattr(t, attr) if t is not None else na_value for t in values] assert equal_geometries(result, expected) @@ -395,7 +446,7 @@ ) def test_unary_predicates(attr): na_value = False - if attr == "is_simple" and geos_version < (3, 8): + if attr == "is_simple" and geos_version < (3, 8) and not compat.USE_PYGEOS: # poly.is_simple raises an error for empty polygon for GEOS < 3.8 with pytest.raises(Exception): T.is_simple @@ -407,7 +458,16 @@ result = getattr(V, attr) - if attr == "is_ring": + if attr == "is_simple" and (geos_version < (3, 8) or compat.USE_PYGEOS): + # poly.is_simple raises an error for empty polygon for GEOS < 3.8 + # with shapely, pygeos always returns False for all GEOS versions + # But even for Shapely with GEOS >= 3.8, empty GeometryCollection + # returns True instead of False + expected = [ + getattr(t, attr) if t is not None and not t.is_empty else na_value + for t in vals + ] + elif attr == "is_ring": expected = [ getattr(t.exterior, attr) if t is not None and t.exterior is not None @@ -527,6 +587,11 @@ @pytest.mark.parametrize("join_style", [JOIN_STYLE.round, JOIN_STYLE.bevel]) @pytest.mark.parametrize("resolution", [16, 25]) def test_buffer(resolution, cap_style, join_style): + if compat.USE_PYGEOS: + # TODO(pygeos) need to further investigate why this test fails + if cap_style == 1 and join_style == 3: + pytest.skip("failing TODO") + na_value = None expected = [ p.buffer(0.1, resolution=resolution, cap_style=cap_style, join_style=join_style) @@ -537,7 +602,12 @@ result = P.buffer( 0.1, resolution=resolution, cap_style=cap_style, join_style=join_style ) + assert equal_geometries(expected, result) + dist = np.array([0.1] * len(P)) + result = P.buffer( + dist, resolution=resolution, cap_style=cap_style, join_style=join_style + ) assert equal_geometries(expected, result) @@ -631,10 +701,10 @@ ) expected = np.array( [ - bounds[:, 0].min(), # minx - bounds[:, 1].min(), # miny - bounds[:, 2].max(), # maxx - bounds[:, 3].max(), # maxy + np.nanmin(bounds[:, 0]), # minx + np.nanmin(bounds[:, 1]), # miny + np.nanmax(bounds[:, 2]), # maxx + np.nanmax(bounds[:, 3]), # maxy ] ) np.testing.assert_allclose(result, expected) @@ -702,7 +772,7 @@ # assert (T.data != T2.data).all() assert T2[-1] is None assert T2[-2].is_empty - assert T[:-2].equals(T2[:-2]).all() + assert T[:-2].geom_equals(T2[:-2]).all() def test_raise_on_bad_sizes(): @@ -745,3 +815,44 @@ result = arr.astype(np.dtype("U10")) assert result.dtype == np.dtype("U10") assert result[0] == multi_poly.wkt[:10] + + +def test_check_crs(): + t1 = T.copy() + t1.crs = 4326 + assert _check_crs(t1, T) is False + assert _check_crs(t1, t1) is True + assert _check_crs(t1, T, allow_none=True) is True + + +def test_crs_mismatch_warn(): + t1 = T.copy() + t2 = T.copy() + t1.crs = 4326 + t2.crs = 3857 + + # two different CRS + with pytest.warns(UserWarning, match="CRS mismatch between the CRS"): + _crs_mismatch_warn(t1, t2) + + # left None + with pytest.warns(UserWarning, match="CRS mismatch between the CRS"): + _crs_mismatch_warn(T, t2) + + # right None + with pytest.warns(UserWarning, match="CRS mismatch between the CRS"): + _crs_mismatch_warn(t1, T) + + +@pytest.mark.parametrize("NA", [None, np.nan]) +def test_isna(NA): + t1 = T.copy() + t1[0] = NA + assert t1[0] is None + + +@pytest.mark.skipif(not compat.PANDAS_GE_10, reason="pd.NA introduced in pandas 1.0") +def test_isna_pdNA(): + t1 = T.copy() + t1[0] = pd.NA + assert t1[0] is None diff -Nru python-geopandas-0.7.0/geopandas/tests/test_compat.py python-geopandas-0.8.1/geopandas/tests/test_compat.py --- python-geopandas-0.7.0/geopandas/tests/test_compat.py 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/test_compat.py 2020-07-15 17:54:36.000000000 +0000 @@ -0,0 +1,30 @@ +import pytest + +from geopandas._compat import import_optional_dependency + + +def test_import_optional_dependency_present(): + # pandas is not optional, but we know it is present + pandas = import_optional_dependency("pandas") + assert pandas is not None + + # module imported normally must be same + import pandas as pd + + assert pandas == pd + + +def test_import_optional_dependency_absent(): + with pytest.raises(ImportError, match="Missing optional dependency 'foo'"): + import_optional_dependency("foo") + + with pytest.raises(ImportError, match="foo is required"): + import_optional_dependency("foo", extra="foo is required") + + +@pytest.mark.parametrize( + "bad_import", [["foo"], 0, False, True, {}, {"foo"}, {"foo": "bar"}] +) +def test_import_optional_dependency_invalid(bad_import): + with pytest.raises(ValueError, match="Invalid module name"): + import_optional_dependency(bad_import) diff -Nru python-geopandas-0.7.0/geopandas/tests/test_config.py python-geopandas-0.8.1/geopandas/tests/test_config.py --- python-geopandas-0.7.0/geopandas/tests/test_config.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/test_config.py 2020-07-15 17:54:36.000000000 +0000 @@ -6,7 +6,7 @@ def test_options(): assert "display_precision: " in repr(geopandas.options) - assert dir(geopandas.options) == ["display_precision"] + assert dir(geopandas.options) == ["display_precision", "use_pygeos"] with pytest.raises(AttributeError): geopandas.options.non_existing_option diff -Nru python-geopandas-0.7.0/geopandas/tests/test_crs.py python-geopandas-0.8.1/geopandas/tests/test_crs.py --- python-geopandas-0.7.0/geopandas/tests/test_crs.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/test_crs.py 2020-07-15 17:54:36.000000000 +0000 @@ -1,11 +1,16 @@ from distutils.version import LooseVersion +import os + +import random import numpy as np +import pandas as pd -from shapely.geometry import Point +from shapely.geometry import Point, Polygon, LineString import pyproj -from geopandas import GeoDataFrame, points_from_xy +from geopandas import GeoSeries, GeoDataFrame, points_from_xy, datasets, read_file +from geopandas.array import from_shapely, from_wkb, from_wkt, GeometryArray from geopandas.testing import assert_geodataframe_equal import pytest @@ -106,7 +111,12 @@ @pytest.mark.filterwarnings("ignore:'\\+init:DeprecationWarning") +@pytest.mark.filterwarnings("ignore:'\\+init:FutureWarning") def test_transform2(epsg4326, epsg26918): + # with PROJ >= 7, the transformation using EPSG code vs proj4 string is + # slightly different due to use of grid files or not -> turn off network + # to not use grid files at all for this test + os.environ["PROJ_NETWORK"] = "OFF" df = df_epsg26918() lonlat = df.to_crs(**epsg4326) utm = lonlat.to_crs(**epsg26918) @@ -128,3 +138,476 @@ df = df_epsg26918() utm = df.to_crs(df.crs) assert_geodataframe_equal(df, utm, check_less_precise=True) + + +# Test CRS on GeometryArray level +class TestGeometryArrayCRS: + def setup_method(self): + self.osgb = pyproj.CRS(27700) + self.wgs = pyproj.CRS(4326) + + self.geoms = [Point(0, 0), Point(1, 1)] + self.polys = [ + Polygon([(random.random(), random.random()) for i in range(3)]) + for _ in range(10) + ] + self.arr = from_shapely(self.polys, crs=27700) + + def test_array(self): + arr = from_shapely(self.geoms) + arr.crs = 27700 + assert arr.crs == self.osgb + + arr = from_shapely(self.geoms, crs=27700) + assert arr.crs == self.osgb + + arr = GeometryArray(arr) + assert arr.crs == self.osgb + + arr = GeometryArray(arr, crs=4326) + assert arr.crs == self.wgs + + def test_series(self): + s = GeoSeries(crs=27700) + assert s.crs == self.osgb + assert s.values.crs == self.osgb + + arr = from_shapely(self.geoms) + s = GeoSeries(arr, crs=27700) + assert s.crs == self.osgb + assert s.values.crs == self.osgb + + # manually change CRS + s.crs = 4326 + assert s.crs == self.wgs + assert s.values.crs == self.wgs + + s = GeoSeries(self.geoms, crs=27700) + assert s.crs == self.osgb + assert s.values.crs == self.osgb + + arr = from_shapely(self.geoms, crs=27700) + s = GeoSeries(arr) + assert s.crs == self.osgb + assert s.values.crs == self.osgb + + with pytest.warns(FutureWarning): + s = GeoSeries(arr, crs=4326) + assert s.crs == self.osgb + + @pytest.mark.filterwarnings("ignore:Assigning CRS") + def test_dataframe(self): + arr = from_shapely(self.geoms, crs=27700) + df = GeoDataFrame(geometry=arr) + assert df.crs == self.osgb + assert df.geometry.crs == self.osgb + assert df.geometry.values.crs == self.osgb + + arr = from_shapely(self.geoms) + s = GeoSeries(arr, crs=27700) + df = GeoDataFrame(geometry=s) + assert df.crs == self.osgb + assert df.geometry.crs == self.osgb + assert df.geometry.values.crs == self.osgb + + # different passed CRS than array CRS is ignored + with pytest.warns(FutureWarning): + df = GeoDataFrame(geometry=s, crs=4326) + assert df.crs == self.osgb + assert df.geometry.crs == self.osgb + assert df.geometry.values.crs == self.osgb + with pytest.warns(FutureWarning): + GeoDataFrame(geometry=s, crs=4326) + with pytest.warns(FutureWarning): + GeoDataFrame({"data": [1, 2], "geometry": s}, crs=4326) + with pytest.warns(FutureWarning): + GeoDataFrame(df, crs=4326).crs + + # manually change CRS + arr = from_shapely(self.geoms) + s = GeoSeries(arr, crs=27700) + df = GeoDataFrame(geometry=s) + df.crs = 4326 + assert df.crs == self.wgs + assert df.geometry.crs == self.wgs + assert df.geometry.values.crs == self.wgs + + df = GeoDataFrame(self.geoms, columns=["geom"], crs=27700) + assert df.crs == self.osgb + df = df.set_geometry("geom") + assert df.crs == self.osgb + assert df.geometry.crs == self.osgb + assert df.geometry.values.crs == self.osgb + assert df.geom.crs == self.osgb + assert df.geom.values.crs == self.osgb + + df = GeoDataFrame(geometry=self.geoms, crs=27700) + assert df.crs == self.osgb + assert df.geometry.crs == self.osgb + assert df.geometry.values.crs == self.osgb + + df = GeoDataFrame(crs=27700) + df = df.set_geometry(self.geoms) + assert df.crs == self.osgb + assert df.geometry.crs == self.osgb + assert df.geometry.values.crs == self.osgb + + # new geometry with set CRS has priority over GDF CRS + df = GeoDataFrame(crs=27700) + df = df.set_geometry(self.geoms, crs=4326) + assert df.crs == self.wgs + assert df.geometry.crs == self.wgs + assert df.geometry.values.crs == self.wgs + + df = GeoDataFrame() + df = df.set_geometry(s) + assert df.crs == self.osgb + assert df.geometry.crs == self.osgb + assert df.geometry.values.crs == self.osgb + + arr = from_shapely(self.geoms, crs=27700) + df = GeoDataFrame() + df = df.set_geometry(arr) + assert df.crs == self.osgb + assert df.geometry.crs == self.osgb + assert df.geometry.values.crs == self.osgb + + arr = from_shapely(self.geoms) + df = GeoDataFrame({"col1": [1, 2], "geometry": arr}, crs=4326) + assert df.crs == self.wgs + assert df.geometry.crs == self.wgs + assert df.geometry.values.crs == self.wgs + + arr = from_shapely(self.geoms, crs=4326) + df = GeoDataFrame({"col1": [1, 2], "geometry": arr}) + assert df.crs == self.wgs + assert df.geometry.crs == self.wgs + assert df.geometry.values.crs == self.wgs + + # geometry column without geometry + df = GeoDataFrame({"geometry": [0, 1]}) + df.crs = 27700 + assert df.crs == self.osgb + + @pytest.mark.parametrize( + "scalar", [None, Point(0, 0), LineString([(0, 0), (1, 1)])] + ) + def test_scalar(self, scalar): + with pytest.warns(FutureWarning): + df = GeoDataFrame() + df.crs = 4326 + df["geometry"] = scalar + assert df.crs == self.wgs + assert df.geometry.crs == self.wgs + assert df.geometry.values.crs == self.wgs + + def test_read_file(self): + nybb_filename = datasets.get_path("nybb") + df = read_file(nybb_filename) + assert df.crs == pyproj.CRS(2263) + assert df.geometry.crs == pyproj.CRS(2263) + assert df.geometry.values.crs == pyproj.CRS(2263) + + def test_multiple_geoms(self): + arr = from_shapely(self.geoms, crs=27700) + s = GeoSeries(self.geoms, crs=4326) + df = GeoDataFrame(s, geometry=arr, columns=["col1"]) + assert df.crs == self.osgb + assert df.geometry.crs == self.osgb + assert df.geometry.values.crs == self.osgb + assert df.col1.crs == self.wgs + assert df.col1.values.crs == self.wgs + + def test_multiple_geoms_set_geom(self): + arr = from_shapely(self.geoms, crs=27700) + s = GeoSeries(self.geoms, crs=4326) + df = GeoDataFrame(s, geometry=arr, columns=["col1"]) + df = df.set_geometry("col1") + assert df.crs == self.wgs + assert df.geometry.crs == self.wgs + assert df.geometry.values.crs == self.wgs + assert df["geometry"].crs == self.osgb + assert df["geometry"].values.crs == self.osgb + + def test_assign_cols(self): + arr = from_shapely(self.geoms, crs=27700) + s = GeoSeries(self.geoms, crs=4326) + df = GeoDataFrame(s, geometry=arr, columns=["col1"]) + df["geom2"] = s + df["geom3"] = s.values + df["geom4"] = from_shapely(self.geoms) + assert df.crs == self.osgb + assert df.geometry.crs == self.osgb + assert df.geometry.values.crs == self.osgb + assert df.geom2.crs == self.wgs + assert df.geom2.values.crs == self.wgs + assert df.geom3.crs == self.wgs + assert df.geom3.values.crs == self.wgs + assert df.geom4.crs is None + assert df.geom4.values.crs is None + + def test_copy(self): + arr = from_shapely(self.geoms, crs=27700) + s = GeoSeries(self.geoms, crs=4326) + df = GeoDataFrame(s, geometry=arr, columns=["col1"]) + + arr_copy = arr.copy() + assert arr_copy.crs == arr.crs + + s_copy = s.copy() + assert s_copy.crs == s.crs + assert s_copy.values.crs == s.values.crs + + df_copy = df.copy() + assert df_copy.crs == df.crs + assert df_copy.geometry.crs == df.geometry.crs + assert df_copy.geometry.values.crs == df.geometry.values.crs + assert df_copy.col1.crs == df.col1.crs + assert df_copy.col1.values.crs == df.col1.values.crs + + def test_rename(self): + arr = from_shapely(self.geoms, crs=27700) + s = GeoSeries(self.geoms, crs=4326) + df = GeoDataFrame(s, geometry=arr, columns=["col1"]) + df = df.rename(columns={"geometry": "geom"}).set_geometry("geom") + assert df.crs == self.osgb + assert df.geometry.crs == self.osgb + assert df.geometry.values.crs == self.osgb + + df = df.rename_geometry("geom2") + assert df.crs == self.osgb + assert df.geometry.crs == self.osgb + assert df.geometry.values.crs == self.osgb + + df = df.rename(columns={"col1": "column1"}) + assert df.column1.crs == self.wgs + assert df.column1.values.crs == self.wgs + + def test_to_crs(self): + s = GeoSeries(self.geoms, crs=27700) + s = s.to_crs(4326) + assert s.crs == self.wgs + assert s.values.crs == self.wgs + + df = GeoDataFrame(geometry=s) + assert df.crs == self.wgs + df = df.to_crs(27700) + assert df.crs == self.osgb + assert df.geometry.crs == self.osgb + assert df.geometry.values.crs == self.osgb + + # make sure that only active geometry is transformed + arr = from_shapely(self.geoms, crs=4326) + df["col1"] = arr + df = df.to_crs(3857) + assert df.col1.crs == self.wgs + assert df.col1.values.crs == self.wgs + + def test_from_shapely(self): + arr = from_shapely(self.geoms, crs=27700) + assert arr.crs == self.osgb + + def test_from_wkb(self): + L_wkb = [p.wkb for p in self.geoms] + arr = from_wkb(L_wkb, crs=27700) + assert arr.crs == self.osgb + + def test_from_wkt(self): + L_wkt = [p.wkt for p in self.geoms] + arr = from_wkt(L_wkt, crs=27700) + assert arr.crs == self.osgb + + def test_points_from_xy(self): + df = pd.DataFrame([{"x": x, "y": x, "z": x} for x in range(10)]) + arr = points_from_xy(df["x"], df["y"], crs=27700) + assert arr.crs == self.osgb + + # setting CRS in GeoSeries should not set it in passed array without CRS + def test_original(self): + arr = from_shapely(self.geoms) + s = GeoSeries(arr, crs=27700) + assert arr.crs is None + assert s.crs == self.osgb + + def test_ops(self): + arr = self.arr + bound = arr.boundary + assert bound.crs == self.osgb + + cent = arr.centroid + assert cent.crs == self.osgb + + hull = arr.convex_hull + assert hull.crs == self.osgb + + envelope = arr.envelope + assert envelope.crs == self.osgb + + exterior = arr.exterior + assert exterior.crs == self.osgb + + representative_point = arr.representative_point() + assert representative_point.crs == self.osgb + + def test_binary_ops(self): + arr = self.arr + quads = [] + while len(quads) < 10: + geom = Polygon([(random.random(), random.random()) for i in range(4)]) + if geom.is_valid: + quads.append(geom) + + arr2 = from_shapely(quads, crs=27700) + + difference = arr.difference(arr2) + assert difference.crs == self.osgb + + intersection = arr.intersection(arr2) + assert intersection.crs == self.osgb + + symmetric_difference = arr.symmetric_difference(arr2) + assert symmetric_difference.crs == self.osgb + + union = arr.union(arr2) + assert union.crs == self.osgb + + def test_other(self): + arr = self.arr + + buffer = arr.buffer(5) + assert buffer.crs == self.osgb + + interpolate = arr.exterior.interpolate(0.1) + assert interpolate.crs == self.osgb + + simplify = arr.simplify(5) + assert simplify.crs == self.osgb + + @pytest.mark.parametrize( + "attr, arg", + [ + ("affine_transform", ([0, 1, 1, 0, 0, 0],)), + ("translate", ()), + ("rotate", (10,)), + ("scale", ()), + ("skew", ()), + ], + ) + def test_affinity_methods(self, attr, arg): + result = getattr(self.arr, attr)(*arg) + + assert result.crs == self.osgb + + def test_slice(self): + s = GeoSeries(self.arr, crs=27700) + assert s.iloc[1:].values.crs == self.osgb + + df = GeoDataFrame({"col1": self.arr}, geometry=s) + assert df.iloc[1:].geometry.values.crs == self.osgb + assert df.iloc[1:].col1.values.crs == self.osgb + + def test_concat(self): + s = GeoSeries(self.arr, crs=27700) + assert pd.concat([s, s]).values.crs == self.osgb + + df = GeoDataFrame({"col1": from_shapely(self.geoms, crs=4326)}, geometry=s) + assert pd.concat([df, df]).geometry.values.crs == self.osgb + assert pd.concat([df, df]).col1.values.crs == self.wgs + + def test_merge(self): + arr = from_shapely(self.geoms, crs=27700) + s = GeoSeries(self.geoms, crs=4326) + df = GeoDataFrame({"col1": s}, geometry=arr) + df2 = GeoDataFrame({"col2": s}, geometry=arr).rename_geometry("geom") + merged = df.merge(df2, left_index=True, right_index=True) + assert merged.col1.values.crs == self.wgs + assert merged.geometry.values.crs == self.osgb + assert merged.col2.values.crs == self.wgs + assert merged.geom.values.crs == self.osgb + assert merged.crs == self.osgb + + # CRS should be assigned to geometry + def test_deprecation(self): + with pytest.warns(FutureWarning): + GeoDataFrame([], crs=27700) + + with pytest.warns(FutureWarning): + df = GeoDataFrame([]) + df.crs = 27700 + + # make sure that geometry column from list has CRS (__setitem__) + def test_setitem_geometry(self): + arr = from_shapely(self.geoms, crs=27700) + df = GeoDataFrame({"col1": [0, 1]}, geometry=arr) + + df["geometry"] = [g for g in df.geometry] + assert df.geometry.values.crs == self.osgb + + df2 = GeoDataFrame({"col1": [0, 1]}, geometry=arr) + df2["geometry"] = from_shapely(self.geoms, crs=4326) + assert df2.geometry.values.crs == self.wgs + + def test_astype(self): + arr = from_shapely(self.geoms, crs=27700) + df = GeoDataFrame({"col1": [0, 1]}, geometry=arr) + df2 = df.astype({"col1": str}) + assert df2.crs == self.osgb + + def test_apply(self): + s = GeoSeries(self.arr) + assert s.crs == 27700 + + # apply preserves the CRS if the result is a GeoSeries + result = s.apply(lambda x: x.centroid) + assert result.crs == 27700 + + +class TestSetCRS: + @pytest.mark.parametrize( + "constructor", + [ + lambda geoms, crs: GeoSeries(geoms, crs=crs), + lambda geoms, crs: GeoDataFrame(geometry=geoms, crs=crs), + ], + ids=["geoseries", "geodataframe"], + ) + def test_set_crs(self, constructor): + naive = constructor([Point(0, 0), Point(1, 1)], crs=None) + assert naive.crs is None + + # by default returns a copy + result = naive.set_crs(crs="EPSG:4326") + assert result.crs == "EPSG:4326" + assert naive.crs is None + + result = naive.set_crs(epsg=4326) + assert result.crs == "EPSG:4326" + assert naive.crs is None + + # with inplace=True + result = naive.set_crs(crs="EPSG:4326", inplace=True) + assert result is naive + assert result.crs == naive.crs == "EPSG:4326" + + # raise for non-naive when crs would be overridden + non_naive = constructor([Point(0, 0), Point(1, 1)], crs="EPSG:4326") + assert non_naive.crs == "EPSG:4326" + with pytest.raises(ValueError, match="already has a CRS"): + non_naive.set_crs("EPSG:3857") + + # allow for equal crs + result = non_naive.set_crs("EPSG:4326") + assert result.crs == "EPSG:4326" + + # replace with allow_override=True + result = non_naive.set_crs("EPSG:3857", allow_override=True) + assert non_naive.crs == "EPSG:4326" + assert result.crs == "EPSG:3857" + + result = non_naive.set_crs("EPSG:3857", allow_override=True, inplace=True) + assert non_naive.crs == "EPSG:3857" + assert result.crs == "EPSG:3857" + + # raise error when no crs is passed + with pytest.raises(ValueError): + naive.set_crs(crs=None, epsg=None) diff -Nru python-geopandas-0.7.0/geopandas/tests/test_extension_array.py python-geopandas-0.8.1/geopandas/tests/test_extension_array.py --- python-geopandas-0.7.0/geopandas/tests/test_extension_array.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/test_extension_array.py 2020-07-15 17:54:36.000000000 +0000 @@ -418,11 +418,15 @@ class TestMethods(extension_tests.BaseMethodsTests): - @no_sorting + @not_yet_implemented @pytest.mark.parametrize("dropna", [True, False]) def test_value_counts(self, all_data, dropna): pass + @not_yet_implemented + def test_value_counts_with_normalize(self, data): + pass + @no_sorting def test_argsort(self, data_for_sorting): result = pd.Series(data_for_sorting).argsort() @@ -493,6 +497,18 @@ def test_argsort_missing_array(self): pass + @no_sorting + def test_argmin_argmax(self): + pass + + @no_sorting + def test_argmin_argmax_empty_array(self): + pass + + @no_sorting + def test_argmin_argmax_all_na(self): + pass + class TestCasting(extension_tests.BaseCastingTests): pass diff -Nru python-geopandas-0.7.0/geopandas/tests/test_geocode.py python-geopandas-0.8.1/geopandas/tests/test_geocode.py --- python-geopandas-0.7.0/geopandas/tests/test_geocode.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/test_geocode.py 2020-07-15 17:54:36.000000000 +0000 @@ -98,7 +98,14 @@ assert "address" in df row = df.loc["b"] - assert len(row["geometry"].coords) == 0 + # The shapely.geometry.Point() is actually a GeometryCollection, and thus + # gets converted to that in conversion to pygeos. When converting back + # on access, you now get a GeometryCollection object instead of Point, + # which has no coords + # see https://github.com/Toblerity/Shapely/issues/742/#issuecomment-545296708 + # TODO we should probably replace this with a missing value instead of point? + # assert len(row["geometry"].coords) == 0 + assert row["geometry"].is_empty assert np.isnan(row["address"]) diff -Nru python-geopandas-0.7.0/geopandas/tests/test_geodataframe.py python-geopandas-0.8.1/geopandas/tests/test_geodataframe.py --- python-geopandas-0.7.0/geopandas/tests/test_geodataframe.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/test_geodataframe.py 2020-07-15 17:54:36.000000000 +0000 @@ -12,10 +12,10 @@ import geopandas from geopandas import GeoDataFrame, GeoSeries, read_file -from geopandas.array import GeometryArray, GeometryDtype +from geopandas.array import GeometryArray, GeometryDtype, from_shapely from geopandas.testing import assert_geodataframe_equal, assert_geoseries_equal -from geopandas.tests.util import PACKAGE_DIR, connect, create_postgis, validate_boro_df +from geopandas.tests.util import PACKAGE_DIR, validate_boro_df from pandas.testing import assert_frame_equal, assert_index_equal, assert_series_equal import pytest @@ -62,6 +62,7 @@ with pytest.raises(CRSError): df.set_geometry(geom2, crs="dummy_crs") + @pytest.mark.filterwarnings("ignore:Geometry is in a geographic CRS") def test_geo_getitem(self): data = { "A": range(5), @@ -77,6 +78,12 @@ assert not isinstance(df["geometry"], GeoSeries) assert isinstance(df["location"], GeoSeries) + df["buff"] = df.buffer(1) + assert isinstance(df["buff"], GeoSeries) + + df["array"] = from_shapely([Point(x, y) for x, y in zip(range(5), range(5))]) + assert isinstance(df["array"], GeoSeries) + data["geometry"] = [Point(x + 1, y - 1) for x, y in zip(range(5), range(5))] df = GeoDataFrame(data, crs=self.crs) assert isinstance(df.geometry, GeoSeries) @@ -159,7 +166,7 @@ assert_geoseries_equal(df["geometry"], new_geom) # new crs - gs = GeoSeries(new_geom, crs="epsg:3857") + gs = new_geom.to_crs(crs="epsg:3857") df.geometry = gs assert df.crs == "epsg:3857" @@ -208,7 +215,7 @@ df2 = self.df.set_geometry(geom) assert self.df is not df2 - assert_geoseries_equal(df2.geometry, geom) + assert_geoseries_equal(df2.geometry, geom, check_crs=False) assert_geoseries_equal(self.df.geometry, original_geom) assert_geoseries_equal(self.df["geometry"], self.df.geometry) # unknown column @@ -420,6 +427,29 @@ assert type(df2) is GeoDataFrame assert self.df.crs == df2.crs + def test_to_file_crs(self): + """ + Ensure that the file is written according to the crs + if it is specified + + """ + tempfilename = os.path.join(self.tempdir, "crs.shp") + # save correct CRS + self.df.to_file(tempfilename) + df = GeoDataFrame.from_file(tempfilename) + assert df.crs == self.df.crs + # overwrite CRS + self.df.to_file(tempfilename, crs=3857) + df = GeoDataFrame.from_file(tempfilename) + assert df.crs == "epsg:3857" + + # specify CRS for gdf without one + df2 = self.df.copy() + df2.crs = None + df2.to_file(tempfilename, crs=2263) + df = GeoDataFrame.from_file(tempfilename) + assert df.crs == "epsg:2263" + def test_bool_index(self): # Find boros with 'B' in their name df = self.df[self.df["BoroName"].str.contains("B")] @@ -476,6 +506,23 @@ ) assert_frame_equal(expected, result) + def test_from_features_geom_interface_feature(self): + class Placemark(object): + def __init__(self, geom, val): + self.__geo_interface__ = { + "type": "Feature", + "properties": {"a": val}, + "geometry": geom.__geo_interface__, + } + + p1 = Point(1, 1) + f1 = Placemark(p1, 0) + p2 = Point(3, 3) + f2 = Placemark(p2, 0) + df = GeoDataFrame.from_features([f1, f2]) + assert sorted(df.columns) == ["a", "geometry"] + assert df.geometry.tolist() == [p1, p2] + def test_from_feature_collection(self): data = { "name": ["a", "b", "c"], @@ -501,33 +548,6 @@ res = GeoDataFrame.from_features(gdf) assert_frame_equal(res, expected) - def test_from_postgis_default(self): - con = connect("test_geopandas") - if con is None or not create_postgis(self.df): - raise pytest.skip() - - try: - sql = "SELECT * FROM nybb;" - df = GeoDataFrame.from_postgis(sql, con) - finally: - con.close() - - validate_boro_df(df, case_sensitive=False) - - def test_from_postgis_custom_geom_col(self): - con = connect("test_geopandas") - geom_col = "the_geom" - if con is None or not create_postgis(self.df, geom_col=geom_col): - raise pytest.skip() - - try: - sql = "SELECT * FROM nybb;" - df = GeoDataFrame.from_postgis(sql, con, geom_col=geom_col) - finally: - con.close() - - validate_boro_df(df, case_sensitive=False) - def test_dataframe_to_geodataframe(self): df = pd.DataFrame( {"A": range(len(self.df)), "location": list(self.df.geometry)}, @@ -600,6 +620,11 @@ result = list(df_only_numerical_cols.iterfeatures(na="keep"))[0] assert type(result["properties"]["Shape_Leng"]) is float + # geometry not set + df = GeoDataFrame({"values": [0, 1], "geom": [Point(0, 1), Point(1, 0)]}) + with pytest.raises(AttributeError): + list(df.iterfeatures()) + def test_geodataframe_geojson_no_bbox(self): geo = self.df._to_geo(na="null", show_bbox=False) assert "bbox" not in geo.keys() diff -Nru python-geopandas-0.7.0/geopandas/tests/test_geom_methods.py python-geopandas-0.8.1/geopandas/tests/test_geom_methods.py --- python-geopandas-0.7.0/geopandas/tests/test_geom_methods.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/test_geom_methods.py 2020-07-15 17:54:36.000000000 +0000 @@ -12,6 +12,7 @@ from geopandas.base import GeoPandasBase from geopandas.tests.util import assert_geoseries_equal, geom_almost_equals, geom_equals +from geopandas import _compat as compat from pandas.testing import assert_frame_equal, assert_series_equal import pytest @@ -29,6 +30,8 @@ self.t2 = Polygon([(0, 0), (1, 1), (0, 1)]) self.t3 = Polygon([(2, 0), (3, 0), (3, 1)]) self.sq = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]) + self.t4 = Polygon([(0, 0), (3, 0), (3, 3), (0, 2)]) + self.t5 = Polygon([(2, 0), (3, 0), (3, 3), (2, 3)]) self.inner_sq = Polygon( [(0.25, 0.25), (0.75, 0.25), (0.75, 0.75), (0.25, 0.75)] ) @@ -66,6 +69,8 @@ self.l2 = LineString([(0, 0), (1, 0), (1, 1), (0, 1)]) self.g5 = GeoSeries([self.l1, self.l2]) self.g6 = GeoSeries([self.p0, self.t3]) + self.g7 = GeoSeries([self.sq, self.t4]) + self.g8 = GeoSeries([self.t1, self.t5]) self.empty = GeoSeries([]) self.all_none = GeoSeries([None, None]) self.empty_poly = Polygon() @@ -83,6 +88,9 @@ self.gdf2 = GeoDataFrame( {"geometry": self.g1, "col3": [4, 5], "col4": ["rand", "string"]} ) + self.gdf3 = GeoDataFrame( + {"geometry": self.g3, "col3": [4, 5], "col4": ["rand", "string"]} + ) def _test_unary_real(self, op, expected, a): """ Tests for 'area', 'length', 'is_valid', etc. """ @@ -123,6 +131,7 @@ """ The operators only have GeoSeries on the left, but can have GeoSeries or GeoDataFrame on the right. + If GeoDataFrame is on the left, geometry column is used. """ if isinstance(expected, GeoPandasBase): @@ -220,9 +229,10 @@ def test_intersection(self): self._test_binary_topological("intersection", self.t1, self.g1, self.g2) - self._test_binary_topological( - "intersection", self.all_none, self.g1, self.empty - ) + with pytest.warns(UserWarning, match="The indices .+ different"): + self._test_binary_topological( + "intersection", self.all_none, self.g1, self.empty + ) def test_union_series(self): self._test_binary_topological("union", self.sq, self.g1, self.g2) @@ -275,6 +285,10 @@ expected = Series(np.array([0.5, np.nan]), index=self.na_none.index) self._test_unary_real("area", expected, self.na_none) + def test_area_crs_warn(self): + with pytest.warns(UserWarning, match="Geometry is in a geographic CRS"): + self.g4.area + def test_bounds(self): # Set columns to get the order right expected = DataFrame( @@ -324,6 +338,10 @@ expected = Series(np.array([2 + np.sqrt(2), np.nan]), index=self.na_none.index) self._test_unary_real("length", expected, self.na_none) + def test_length_crs_warn(self): + with pytest.warns(UserWarning, match="Geometry is in a geographic CRS"): + self.g4.length + def test_crosses(self): expected = [False, False, False, False, False, False, False] assert_array_dtype_equal(expected, self.g0.crosses(self.t1)) @@ -362,6 +380,10 @@ expected = Series(np.array([np.sqrt(4 ** 2 + 4 ** 2), np.nan]), self.g6.index) assert_array_dtype_equal(expected, self.g6.distance(self.na_none)) + def test_distance_crs_warning(self): + with pytest.warns(UserWarning, match="Geometry is in a geographic CRS"): + self.g4.distance(self.p0) + def test_intersects(self): expected = [True, True, True, True, True, False, False] assert_array_dtype_equal(expected, self.g0.intersects(self.t1)) @@ -396,6 +418,31 @@ expected = [True, True, True, True, True, False, False] assert_array_dtype_equal(expected, self.g0.within(self.sq)) + def test_covers_itself(self): + # Each polygon in a Series covers itself + res = self.g1.covers(self.g1) + exp = Series([True, True]) + assert_series_equal(res, exp) + + def test_covers(self): + res = self.g7.covers(self.g8) + exp = Series([True, False]) + assert_series_equal(res, exp) + + def test_covers_inverse(self): + res = self.g8.covers(self.g7) + exp = Series([False, False]) + assert_series_equal(res, exp) + + @pytest.mark.skipif( + not compat.USE_PYGEOS, + reason="covered_by is only implemented for pygeos, not shapely", + ) + def test_covered_by(self): + res = self.g1.covered_by(self.g1) + exp = Series([True, True]) + assert_series_equal(res, exp) + def test_is_valid(self): expected = Series(np.array([True] * len(self.g1)), self.g1.index) self._test_unary_real("is_valid", expected, self.g1) @@ -438,6 +485,10 @@ points = GeoSeries([point for i in range(3)]) assert_geoseries_equal(polygons.centroid, points) + def test_centroid_crs_warn(self): + with pytest.warns(UserWarning, match="Geometry is in a geographic CRS"): + self.g4.centroid + def test_convex_hull(self): # the convex hull of a square should be the same as the square squares = GeoSeries([self.sq for i in range(3)]) @@ -488,6 +539,12 @@ with pytest.raises(ValueError): self.g5.interpolate(distances) + def test_interpolate_crs_warning(self): + g5_crs = self.g5.copy() + g5_crs.crs = 4326 + with pytest.warns(UserWarning, match="Geometry is in a geographic CRS"): + g5_crs.interpolate(1) + def test_project(self): expected = Series([2.0, 1.5], index=self.g5.index) p = Point(1.0, 0.5) @@ -606,6 +663,16 @@ result = s.buffer(np.array([0, 0, 0])) assert_geoseries_equal(result, s) + def test_buffer_crs_warn(self): + with pytest.warns(UserWarning, match="Geometry is in a geographic CRS"): + self.g4.buffer(1) + + with pytest.warns(None) as record: + # do not warn for 0 + self.g4.buffer(0) + + assert len(record) == 0 + def test_envelope(self): e = self.g3.envelope assert np.all(e.geom_equals(self.sq)) @@ -653,27 +720,64 @@ expected_df = expected_df.set_index(expected_index) assert_frame_equal(test_df, expected_df) + @pytest.mark.parametrize("index_name", [None, "test"]) + def test_explode_geodataframe_level_1(self, index_name): + # GH1393 + s = GeoSeries([MultiPoint([Point(1, 2), Point(2, 3)]), Point(5, 5)]) + df = GeoDataFrame({"level_1": [1, 2], "geometry": s}) + df.index.name = index_name + + test_df = df.explode() + + expected_s = GeoSeries([Point(1, 2), Point(2, 3), Point(5, 5)]) + expected_df = GeoDataFrame({"level_1": [1, 1, 2], "geometry": expected_s}) + expected_index = MultiIndex( + [[0, 1], [0, 1]], # levels + [[0, 0, 1], [0, 1, 0]], # labels/codes + names=[index_name, None], + ) + expected_df = expected_df.set_index(expected_index) + if not compat.PANDAS_GE_024: + expected_df = expected_df[["level_1", "geometry"]] + assert_frame_equal(test_df, expected_df) + # # Test '&', '|', '^', and '-' - # The left can only be a GeoSeries. The right hand side can be a - # GeoSeries, GeoDataFrame or Shapely geometry # def test_intersection_operator(self): - self._test_binary_operator("__and__", self.t1, self.g1, self.g2) + with pytest.warns(DeprecationWarning): + self._test_binary_operator("__and__", self.t1, self.g1, self.g2) + with pytest.warns(DeprecationWarning): + self._test_binary_operator("__and__", self.t1, self.gdf1, self.g2) def test_union_operator(self): - self._test_binary_operator("__or__", self.sq, self.g1, self.g2) + with pytest.warns(DeprecationWarning): + self._test_binary_operator("__or__", self.sq, self.g1, self.g2) + with pytest.warns(DeprecationWarning): + self._test_binary_operator("__or__", self.sq, self.gdf1, self.g2) def test_union_operator_polygon(self): - self._test_binary_operator("__or__", self.sq, self.g1, self.t2) + with pytest.warns(DeprecationWarning): + self._test_binary_operator("__or__", self.sq, self.g1, self.t2) + with pytest.warns(DeprecationWarning): + self._test_binary_operator("__or__", self.sq, self.gdf1, self.t2) def test_symmetric_difference_operator(self): - self._test_binary_operator("__xor__", self.sq, self.g3, self.g4) + with pytest.warns(DeprecationWarning): + self._test_binary_operator("__xor__", self.sq, self.g3, self.g4) + with pytest.warns(DeprecationWarning): + self._test_binary_operator("__xor__", self.sq, self.gdf3, self.g4) def test_difference_series2(self): expected = GeoSeries([GeometryCollection(), self.t2]) - self._test_binary_operator("__sub__", expected, self.g1, self.g2) + with pytest.warns(DeprecationWarning): + self._test_binary_operator("__sub__", expected, self.g1, self.g2) + with pytest.warns(DeprecationWarning): + self._test_binary_operator("__sub__", expected, self.gdf1, self.g2) def test_difference_poly2(self): expected = GeoSeries([self.t1, self.t1]) - self._test_binary_operator("__sub__", expected, self.g1, self.t2) + with pytest.warns(DeprecationWarning): + self._test_binary_operator("__sub__", expected, self.g1, self.t2) + with pytest.warns(DeprecationWarning): + self._test_binary_operator("__sub__", expected, self.gdf1, self.t2) diff -Nru python-geopandas-0.7.0/geopandas/tests/test_geoseries.py python-geopandas-0.8.1/geopandas/tests/test_geoseries.py --- python-geopandas-0.7.0/geopandas/tests/test_geoseries.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/test_geoseries.py 2020-07-15 17:54:36.000000000 +0000 @@ -98,12 +98,39 @@ exp2 = pd.Series([np.nan, 1, 2], index=["A", "B", "C"]) assert_series_equal(res2, exp2) + def test_warning_if_not_aligned(self): + # GH-816 + # Test that warning is issued when operating on non-aligned series + + # _series_op + with pytest.warns(UserWarning, match="The indices .+ different"): + self.a1.contains(self.a2) + + # _geo_op + with pytest.warns(UserWarning, match="The indices .+ different"): + self.a1.union(self.a2) + + def test_no_warning_if_aligned(self): + # GH-816 + # Test that warning is not issued when operating on aligned series + a1, a2 = self.a1.align(self.a2) + + with pytest.warns(None) as warnings: + a1.contains(a2) # _series_op, explicitly aligned + self.g1.intersects(self.g2) # _series_op, implicitly aligned + a2.union(a1) # _geo_op, explicitly aligned + self.g2.intersection(self.g1) # _geo_op, implicitly aligned + + user_warnings = [w for w in warnings if w.category is UserWarning] + assert not user_warnings, user_warnings[0].message + def test_geom_equals(self): assert np.all(self.g1.geom_equals(self.g1)) assert_array_equal(self.g1.geom_equals(self.sq), [False, True]) def test_geom_equals_align(self): - a = self.a1.geom_equals(self.a2) + with pytest.warns(UserWarning, match="The indices .+ different"): + a = self.a1.geom_equals(self.a2) exp = pd.Series([False, True, False], index=["A", "B", "C"]) assert_series_equal(a, exp) @@ -286,6 +313,7 @@ for g in geoms: gs = GeoSeries(g) assert len(gs) == 1 + # accessing elements no longer give identical objects assert gs.iloc[0].equals(g) gs = GeoSeries(g, index=index) diff -Nru python-geopandas-0.7.0/geopandas/tests/test_overlay.py python-geopandas-0.8.1/geopandas/tests/test_overlay.py --- python-geopandas-0.7.0/geopandas/tests/test_overlay.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/test_overlay.py 2020-07-15 17:54:36.000000000 +0000 @@ -14,6 +14,11 @@ DATA = os.path.join(os.path.abspath(os.path.dirname(__file__)), "data", "overlay") +pytestmark = pytest.mark.skipif( + not geopandas.sindex.has_sindex(), reason="overlay requires spatial index" +) + + @pytest.fixture def dfs(request): s1 = GeoSeries( @@ -160,6 +165,33 @@ assert len(result.columns) == len(expected.columns) result = result.reindex(columns=expected.columns) + # the ordering of the spatial index results causes slight deviations + # in the resultant geometries for multipolygons + # for more details on the discussion, see: + # https://github.com/geopandas/geopandas/pull/1338 + # https://github.com/geopandas/geopandas/issues/1337 + + # Temporary workaround below: + + # simplify multipolygon geometry comparison + # since the order of the constituent polygons depends on + # the ordering of spatial indexing results, we cannot + # compare symmetric_difference results directly when the + # resultant geometry is a multipolygon + + # first, check that all bounds and areas are approx equal + # this is a very rough check for multipolygon equality + pd.testing.assert_series_equal( + result.geometry.area, expected.geometry.area, check_less_precise=True + ) + pd.testing.assert_frame_equal( + result.geometry.bounds, expected.geometry.bounds, check_less_precise=True + ) + + # now drop multipolygons + result.geometry[result.geometry.geom_type == "MultiPolygon"] = None + expected.geometry[expected.geometry.geom_type == "MultiPolygon"] = None + assert_geodataframe_equal( result, expected, check_crs=False, check_column_type=False ) @@ -295,6 +327,14 @@ assert result.crs == crs +def test_crs_mismatch(dfs, how): + df1, df2 = dfs + df1.crs = 4326 + df2.crs = 3857 + with pytest.warns(UserWarning, match="CRS mismatch between the CRS"): + overlay(df1, df2, how=how) + + def test_empty_intersection(dfs): df1, df2 = dfs polys3 = GeoSeries( @@ -419,6 +459,15 @@ "{t}_{h}_{s}.geojson".format(t=geom_types, h=how, s=keep_geom_type), ) ) + + # the order depends on the spatial index used + # so we sort the resultant dataframes to get a consistent order + # independently of the spatial index implementation + assert all(expected.columns == result.columns), "Column name mismatch" + cols = list(set(result.columns) - set(["geometry"])) + expected = expected.sort_values(cols, axis=0).reset_index(drop=True) + result = result.sort_values(cols, axis=0).reset_index(drop=True) + assert_geodataframe_equal( result, expected, diff -Nru python-geopandas-0.7.0/geopandas/tests/test_pandas_methods.py python-geopandas-0.8.1/geopandas/tests/test_pandas_methods.py --- python-geopandas-0.7.0/geopandas/tests/test_pandas_methods.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/test_pandas_methods.py 2020-07-15 17:54:36.000000000 +0000 @@ -9,7 +9,7 @@ import geopandas from geopandas import GeoDataFrame, GeoSeries -from geopandas._compat import PANDAS_GE_024, PANDAS_GE_025 +from geopandas._compat import PANDAS_GE_024, PANDAS_GE_025, PANDAS_GE_11 from geopandas.array import from_shapely from geopandas.testing import assert_geodataframe_equal, assert_geoseries_equal @@ -314,6 +314,22 @@ assert_frame_equal(res, exp) +def test_equals(s, df): + # https://github.com/geopandas/geopandas/issues/1420 + s2 = s.copy() + assert s.equals(s2) is True + s2.iloc[0] = None + assert s.equals(s2) is False + + df2 = df.copy() + assert df.equals(df2) is True + df2.loc[0, "geometry"] = Point(10, 10) + assert df.equals(df2) is False + df2 = df.copy() + df2.loc[0, "value1"] = 10 + assert df.equals(df2) is False + + # Missing values @@ -444,11 +460,24 @@ # applying on the geometry column res = df.groupby("value2")["geometry"].apply(lambda x: x.cascaded_union) - exp = pd.Series( - [shapely.geometry.MultiPoint([(0, 0), (2, 2)]), Point(1, 1)], - index=pd.Index([1, 2], name="value2"), - name="geometry", - ) + if PANDAS_GE_11: + exp = GeoSeries( + [shapely.geometry.MultiPoint([(0, 0), (2, 2)]), Point(1, 1)], + index=pd.Index([1, 2], name="value2"), + name="geometry", + ) + else: + exp = pd.Series( + [shapely.geometry.MultiPoint([(0, 0), (2, 2)]), Point(1, 1)], + index=pd.Index([1, 2], name="value2"), + name="geometry", + ) + assert_series_equal(res, exp) + + # apply on geometry column not resulting in new geometry + res = df.groupby("value2")["geometry"].apply(lambda x: x.unary_union.area) + exp = pd.Series([0.0, 0.0], index=pd.Index([1, 2], name="value2"), name="geometry") + assert_series_equal(res, exp) @@ -460,6 +489,26 @@ assert_frame_equal(res, exp) +def test_apply(s): + # function that returns geometry preserves GeoSeries class + def geom_func(geom): + assert isinstance(geom, Point) + return geom + + result = s.apply(geom_func) + assert isinstance(result, GeoSeries) + assert_geoseries_equal(result, s) + + # function that returns non-geometry results in Series + def numeric_func(geom): + assert isinstance(geom, Point) + return geom.x + + result = s.apply(numeric_func) + assert not isinstance(result, GeoSeries) + assert_series_equal(result, pd.Series([0.0, 1.0, 2.0])) + + def test_apply_loc_len1(df): # subset of len 1 with loc -> bug in pandas with inconsistent Block ndim # resulting in bug in apply diff -Nru python-geopandas-0.7.0/geopandas/tests/test_plotting.py python-geopandas-0.8.1/geopandas/tests/test_plotting.py --- python-geopandas-0.7.0/geopandas/tests/test_plotting.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/test_plotting.py 2020-07-15 17:54:36.000000000 +0000 @@ -2,6 +2,7 @@ import warnings import numpy as np +import pandas as pd from shapely.affinity import rotate from shapely.geometry import ( @@ -128,7 +129,7 @@ _check_colors( self.N, ax.collections[0].get_facecolors(), [(0.5, 0.5, 0.5, 0.5)] * self.N ) - with pytest.raises(TypeError): + with pytest.raises((ValueError, TypeError)): self.df.plot(color="not color") with warnings.catch_warnings(record=True) as _: # don't print warning @@ -155,11 +156,24 @@ ax = self.df.plot(column="values", markersize="values") assert (ax.collections[0].get_sizes() == self.df["values"]).all() + def test_markerstyle(self): + ax = self.df2.plot(marker="+") + expected = _style_to_vertices("+") + np.testing.assert_array_equal( + expected, ax.collections[0].get_paths()[0].vertices + ) + def test_style_kwargs(self): ax = self.points.plot(edgecolors="k") assert (ax.collections[0].get_edgecolor() == [0, 0, 0, 1]).all() + def test_style_kwargs_alpha(self): + ax = self.df.plot(alpha=0.7) + np.testing.assert_array_equal([0.7], ax.collections[0].get_alpha()) + with pytest.raises(TypeError): # no list allowed for alpha + ax = self.df.plot(alpha=[0.7, 0.2]) + def test_legend(self): with warnings.catch_warnings(record=True) as _: # don't print warning # legend ignored if color is given. @@ -243,6 +257,54 @@ # colors are repeated for all components within a MultiPolygon _check_colors(2, ax.collections[0].get_facecolors(), ["r"] * 10 + ["b"] * 10) + def test_multipoints_alpha(self): + ax = self.df2.plot(alpha=0.7) + np.testing.assert_array_equal([0.7], ax.collections[0].get_alpha()) + with pytest.raises(TypeError): # no list allowed for alpha + ax = self.df2.plot(alpha=[0.7, 0.2]) + + def test_categories(self): + self.df["cats_object"] = ["cat1", "cat2"] * 5 + self.df["nums"] = [1, 2] * 5 + self.df["singlecat_object"] = ["cat2"] * 10 + self.df["cats"] = pd.Categorical(["cat1", "cat2"] * 5) + self.df["singlecat"] = pd.Categorical( + ["cat2"] * 10, categories=["cat1", "cat2"] + ) + self.df["cats_ordered"] = pd.Categorical( + ["cat2", "cat1"] * 5, categories=["cat2", "cat1"] + ) + + ax1 = self.df.plot("cats_object", legend=True) + ax2 = self.df.plot("cats", legend=True) + ax3 = self.df.plot("singlecat_object", categories=["cat1", "cat2"], legend=True) + ax4 = self.df.plot("singlecat", legend=True) + ax5 = self.df.plot("cats_ordered", legend=True) + ax6 = self.df.plot("nums", categories=[1, 2], legend=True) + + point_colors1 = ax1.collections[0].get_facecolors() + for ax in [ax2, ax3, ax4, ax5, ax6]: + point_colors2 = ax.collections[0].get_facecolors() + np.testing.assert_array_equal(point_colors1[1], point_colors2[1]) + + legend1 = [x.get_markerfacecolor() for x in ax1.get_legend().get_lines()] + for ax in [ax2, ax3, ax4, ax5, ax6]: + legend2 = [x.get_markerfacecolor() for x in ax.get_legend().get_lines()] + np.testing.assert_array_equal(legend1, legend2) + + with pytest.raises(TypeError): + self.df.plot(column="cats_object", categories="non_list") + + with pytest.raises( + ValueError, match="Column contains values not listed in categories." + ): + self.df.plot(column="cats_object", categories=["cat1"]) + + with pytest.raises( + ValueError, match="Cannot specify 'categories' when column has" + ): + self.df.plot(column="cats", categories=["cat1"]) + def test_misssing(self): self.df.loc[0, "values"] = np.nan ax = self.df.plot("values") @@ -325,7 +387,7 @@ _check_colors( self.N, ax.collections[0].get_colors(), [(0.5, 0.5, 0.5, 0.5)] * self.N ) - with pytest.raises(TypeError): + with pytest.raises((TypeError, ValueError)): self.df.plot(color="not color") with warnings.catch_warnings(record=True) as _: # don't print warning @@ -333,26 +395,54 @@ ax = self.df.plot(column="values", color="green") _check_colors(self.N, ax.collections[0].get_colors(), ["green"] * self.N) - def test_style_kwargs(self): - # linestyle (style patterns depend on linewidth, therefore pin to 1) - linestyle = "dashed" - linewidth = 1 - - ax = self.lines.plot(linestyle=linestyle, linewidth=linewidth) - exp_ls = _style_to_linestring_onoffseq(linestyle, linewidth) - for ls in ax.collections[0].get_linestyles(): - assert ls[0] == exp_ls[0] - assert ls[1] == exp_ls[1] - - ax = self.df.plot(linestyle=linestyle, linewidth=linewidth) - for ls in ax.collections[0].get_linestyles(): - assert ls[0] == exp_ls[0] - assert ls[1] == exp_ls[1] - - ax = self.df.plot(column="values", linestyle=linestyle, linewidth=linewidth) - for ls in ax.collections[0].get_linestyles(): - assert ls[0] == exp_ls[0] - assert ls[1] == exp_ls[1] + def test_style_kwargs_linestyle(self): + # single + for ax in [ + self.lines.plot(linestyle=":", linewidth=1), + self.df.plot(linestyle=":", linewidth=1), + self.df.plot(column="values", linestyle=":", linewidth=1), + ]: + assert [(0.0, [1.0, 1.65])] == ax.collections[0].get_linestyle() + + # tuple + ax = self.lines.plot(linestyle=(0, (3, 10, 1, 15)), linewidth=1) + assert [(0, [3, 10, 1, 15])] == ax.collections[0].get_linestyle() + + # multiple + ls = [("dashed", "dotted", "dashdot", "solid")[k % 4] for k in range(self.N)] + exp_ls = [_style_to_linestring_onoffseq(st, 1) for st in ls] + for ax in [ + self.lines.plot(linestyle=ls, linewidth=1), + self.lines.plot(linestyles=ls, linewidth=1), + self.df.plot(linestyle=ls, linewidth=1), + self.df.plot(column="values", linestyle=ls, linewidth=1), + ]: + np.testing.assert_array_equal(exp_ls, ax.collections[0].get_linestyle()) + + def test_style_kwargs_linewidth(self): + # single + for ax in [ + self.lines.plot(linewidth=2), + self.df.plot(linewidth=2), + self.df.plot(column="values", linewidth=2), + ]: + np.testing.assert_array_equal([2], ax.collections[0].get_linewidths()) + + # multiple + lw = [(0, 1, 2, 5.5, 10)[k % 5] for k in range(self.N)] + for ax in [ + self.lines.plot(linewidth=lw), + self.lines.plot(linewidths=lw), + self.df.plot(linewidth=lw), + self.df.plot(column="values", linewidth=lw), + ]: + np.testing.assert_array_equal(lw, ax.collections[0].get_linewidths()) + + def test_style_kwargs_alpha(self): + ax = self.df.plot(alpha=0.7) + np.testing.assert_array_equal([0.7], ax.collections[0].get_alpha()) + with pytest.raises(TypeError): # no list allowed for alpha + ax = self.df.plot(alpha=[0.7, 0.2]) def test_subplots_norm(self): # colors of subplots are the same as for plot (norm is applied) @@ -419,7 +509,7 @@ _check_colors(2, ax.collections[0].get_facecolors(), [(0.5, 0.5, 0.5)] * 2) ax = self.df.plot(color=(0.5, 0.5, 0.5, 0.5)) _check_colors(2, ax.collections[0].get_facecolors(), [(0.5, 0.5, 0.5, 0.5)] * 2) - with pytest.raises(TypeError): + with pytest.raises((TypeError, ValueError)): self.df.plot(color="not color") with warnings.catch_warnings(record=True) as _: # don't print warning @@ -445,7 +535,7 @@ actual_colors = ax.collections[0].get_facecolors() assert np.any(np.not_equal(actual_colors[0], actual_colors[1])) - def test_style_kwargs(self): + def test_style_kwargs_color(self): # facecolor overrides default cmap when color is not set ax = self.polys.plot(facecolor="k") @@ -483,6 +573,38 @@ _check_colors(2, ax.collections[0].get_facecolors(), [(0.5, 0.5, 0.5, 0.5)] * 2) _check_colors(2, ax.collections[0].get_edgecolors(), [(0.4, 0.5, 0.6, 0.5)] * 2) + def test_style_kwargs_linestyle(self): + # single + ax = self.df.plot(linestyle=":", linewidth=1) + assert [(0.0, [1.0, 1.65])] == ax.collections[0].get_linestyle() + + # tuple + ax = self.df.plot(linestyle=(0, (3, 10, 1, 15)), linewidth=1) + assert [(0, [3, 10, 1, 15])] == ax.collections[0].get_linestyle() + + # multiple + ls = ["dashed", "dotted"] + exp_ls = [_style_to_linestring_onoffseq(st, 1) for st in ls] + for ax in [ + self.df.plot(linestyle=ls, linewidth=1), + self.df.plot(linestyles=ls, linewidth=1), + ]: + assert exp_ls == ax.collections[0].get_linestyle() + + def test_style_kwargs_linewidth(self): + # single + ax = self.df.plot(linewidth=2) + np.testing.assert_array_equal([2], ax.collections[0].get_linewidths()) + # multiple + for ax in [self.df.plot(linewidth=[2, 4]), self.df.plot(linewidths=[2, 4])]: + np.testing.assert_array_equal([2, 4], ax.collections[0].get_linewidths()) + + # alpha + ax = self.df.plot(alpha=0.7) + np.testing.assert_array_equal([0.7], ax.collections[0].get_alpha()) + with pytest.raises(TypeError): # no list allowed for alpha + ax = self.df.plot(alpha=[0.7, 0.2]) + def test_legend_kwargs(self): ax = self.df.plot( @@ -516,7 +638,20 @@ assert ax.get_figure().axes[1].get_xlabel() == label_txt - def test_multipolygons(self): + def test_fmt_ignore(self): + # test if fmt is removed if scheme is not passed (it would raise Error) + # GH #1253 + + self.df.plot( + column="values", + categorical=True, + legend=True, + legend_kwds={"fmt": "{:.0f}"}, + ) + + self.df.plot(column="values", legend=True, legend_kwds={"fmt": "{:.0f}"}) + + def test_multipolygons_color(self): # MultiPolygons ax = self.df2.plot() @@ -533,6 +668,41 @@ # colors are repeated for all components within a MultiPolygon _check_colors(4, ax.collections[0].get_facecolors(), ["r", "r", "b", "b"]) + def test_multipolygons_linestyle(self): + # single + ax = self.df2.plot(linestyle=":", linewidth=1) + assert [(0.0, [1.0, 1.65])] == ax.collections[0].get_linestyle() + + # tuple + ax = self.df2.plot(linestyle=(0, (3, 10, 1, 15)), linewidth=1) + assert [(0, [3, 10, 1, 15])] == ax.collections[0].get_linestyle() + + # multiple + ls = ["dashed", "dotted"] + exp_ls = [_style_to_linestring_onoffseq(st, 1) for st in ls for i in range(2)] + for ax in [ + self.df2.plot(linestyle=ls, linewidth=1), + self.df2.plot(linestyles=ls, linewidth=1), + ]: + assert exp_ls == ax.collections[0].get_linestyle() + + def test_multipolygons_linewidth(self): + # single + ax = self.df2.plot(linewidth=2) + np.testing.assert_array_equal([2], ax.collections[0].get_linewidths()) + + # multiple + for ax in [self.df2.plot(linewidth=[2, 4]), self.df2.plot(linewidths=[2, 4])]: + np.testing.assert_array_equal( + [2, 2, 4, 4], ax.collections[0].get_linewidths() + ) + + def test_multipolygons_alpha(self): + ax = self.df2.plot(alpha=0.7) + np.testing.assert_array_equal([0.7], ax.collections[0].get_alpha()) + with pytest.raises(TypeError): # no list allowed for alpha + ax = self.df2.plot(alpha=[0.7, 0.2]) + def test_subplots_norm(self): # colors of subplots are the same as for plot (norm is applied) cmap = matplotlib.cm.viridis_r @@ -628,6 +798,102 @@ ax = self.df.plot(markersize=10) assert ax.collections[2].get_sizes() == [10] + def test_style_kwargs_linestyle(self): + # single + for ax in [ + self.series.plot(linestyle=":", linewidth=1), + self.df.plot(linestyle=":", linewidth=1), + ]: + assert [(0.0, [1.0, 1.65])] == ax.collections[0].get_linestyle() + + # tuple + ax = self.series.plot(linestyle=(0, (3, 10, 1, 15)), linewidth=1) + assert [(0, [3, 10, 1, 15])] == ax.collections[0].get_linestyle() + + @pytest.mark.skip( + reason="array-like style_kwds not supported for mixed geometry types (#1379)" + ) + def test_style_kwargs_linestyle_listlike(self): + # multiple + ls = ["solid", "dotted", "dashdot"] + exp_ls = [_style_to_linestring_onoffseq(style, 1) for style in ls] + for ax in [ + self.series.plot(linestyle=ls, linewidth=1), + self.series.plot(linestyles=ls, linewidth=1), + self.df.plot(linestyles=ls, linewidth=1), + ]: + np.testing.assert_array_equal(exp_ls, ax.collections[0].get_linestyle()) + + def test_style_kwargs_linewidth(self): + # single + ax = self.df.plot(linewidth=2) + np.testing.assert_array_equal([2], ax.collections[0].get_linewidths()) + + @pytest.mark.skip( + reason="array-like style_kwds not supported for mixed geometry types (#1379)" + ) + def test_style_kwargs_linewidth_listlike(self): + # multiple + for ax in [ + self.series.plot(linewidths=[2, 4, 5.5]), + self.series.plot(linewidths=[2, 4, 5.5]), + self.df.plot(linewidths=[2, 4, 5.5]), + ]: + np.testing.assert_array_equal( + [2, 4, 5.5], ax.collections[0].get_linewidths() + ) + + def test_style_kwargs_alpha(self): + ax = self.df.plot(alpha=0.7) + np.testing.assert_array_equal([0.7], ax.collections[0].get_alpha()) + with pytest.raises(TypeError): # no list allowed for alpha + ax = self.df.plot(alpha=[0.7, 0.2, 0.9]) + + +class TestGeographicAspect: + def setup_class(self): + pth = get_path("naturalearth_lowres") + df = read_file(pth) + self.north = df.loc[df.continent == "North America"] + self.north_proj = self.north.to_crs("ESRI:102008") + bounds = self.north.total_bounds + y_coord = np.mean([bounds[1], bounds[3]]) + self.exp = 1 / np.cos(y_coord * np.pi / 180) + + def test_auto(self): + ax = self.north.geometry.plot() + assert ax.get_aspect() == self.exp + ax2 = self.north_proj.geometry.plot() + assert ax2.get_aspect() in ["equal", 1.0] + ax = self.north.plot() + assert ax.get_aspect() == self.exp + ax2 = self.north_proj.plot() + assert ax2.get_aspect() in ["equal", 1.0] + ax3 = self.north.plot("pop_est") + assert ax3.get_aspect() == self.exp + ax4 = self.north_proj.plot("pop_est") + assert ax4.get_aspect() in ["equal", 1.0] + + def test_manual(self): + ax = self.north.geometry.plot(aspect="equal") + assert ax.get_aspect() in ["equal", 1.0] + ax2 = self.north.geometry.plot(aspect=0.5) + assert ax2.get_aspect() == 0.5 + ax3 = self.north_proj.geometry.plot(aspect=0.5) + assert ax3.get_aspect() == 0.5 + ax = self.north.plot(aspect="equal") + assert ax.get_aspect() in ["equal", 1.0] + ax2 = self.north.plot(aspect=0.5) + assert ax2.get_aspect() == 0.5 + ax3 = self.north_proj.plot(aspect=0.5) + assert ax3.get_aspect() == 0.5 + ax = self.north.plot("pop_est", aspect="equal") + assert ax.get_aspect() in ["equal", 1.0] + ax2 = self.north.plot("pop_est", aspect=0.5) + assert ax2.get_aspect() == 0.5 + ax3 = self.north_proj.plot("pop_est", aspect=0.5) + assert ax3.get_aspect() == 0.5 + class TestMapclassifyPlotting: @classmethod @@ -635,10 +901,9 @@ try: import mapclassify # noqa except ImportError: - try: - import pysal # noqa - except ImportError: - pytest.importorskip("mapclassify") + pytest.importorskip("mapclassify") + cls.classifiers = list(mapclassify.classifiers.CLASSIFIERS) + cls.classifiers.remove("UserDefined") pth = get_path("naturalearth_lowres") cls.df = read_file(pth) cls.df["NEGATIVES"] = np.linspace(-10, 10, len(cls.df.index)) @@ -651,18 +916,55 @@ ) labels = [t.get_text() for t in ax.get_legend().get_texts()] expected = [ - u"140.00 - 5217064.00", - u"5217064.00 - 19532732.33", - u"19532732.33 - 1379302771.00", + u"[ 140.00, 5217064.00]", + u"( 5217064.00, 19532732.33]", + u"( 19532732.33, 1379302771.00]", ] assert labels == expected + def test_bin_labels(self): + ax = self.df.plot( + column="pop_est", + scheme="QUANTILES", + k=3, + cmap="OrRd", + legend=True, + legend_kwds={"labels": ["foo", "bar", "baz"]}, + ) + labels = [t.get_text() for t in ax.get_legend().get_texts()] + expected = ["foo", "bar", "baz"] + assert labels == expected + + def test_invalid_labels_length(self): + with pytest.raises(ValueError): + self.df.plot( + column="pop_est", + scheme="QUANTILES", + k=3, + cmap="OrRd", + legend=True, + legend_kwds={"labels": ["foo", "bar"]}, + ) + def test_negative_legend(self): ax = self.df.plot( column="NEGATIVES", scheme="FISHER_JENKS", k=3, cmap="OrRd", legend=True ) labels = [t.get_text() for t in ax.get_legend().get_texts()] - expected = [u"-10.00 - -3.41", u"-3.41 - 3.30", u"3.30 - 10.00"] + expected = [u"[-10.00, -3.41]", u"( -3.41, 3.30]", u"( 3.30, 10.00]"] + assert labels == expected + + def test_fmt(self): + ax = self.df.plot( + column="NEGATIVES", + scheme="FISHER_JENKS", + k=3, + cmap="OrRd", + legend=True, + legend_kwds={"fmt": "{:.0f}"}, + ) + labels = [t.get_text() for t in ax.get_legend().get_texts()] + expected = [u"[-10, -3]", u"( -3, 3]", u"( 3, 10]"] assert labels == expected @pytest.mark.parametrize("scheme", ["FISHER_JENKS", "FISHERJENKS"]) @@ -670,6 +972,11 @@ ax = self.df.plot(column="NEGATIVES", scheme=scheme, k=3, legend=True) assert len(ax.get_legend().get_texts()) == 3 + def test_schemes(self): + # test if all available classifiers pass + for scheme in self.classifiers: + self.df.plot(column="pop_est", scheme=scheme, legend=True) + def test_classification_kwds(self): ax = self.df.plot( column="pop_est", @@ -680,7 +987,7 @@ legend=True, ) labels = [t.get_text() for t in ax.get_legend().get_texts()] - expected = ["140.00 - 9961396.00", "9961396.00 - 1379302771.00"] + expected = ["[ 140.00, 9961396.00]", "( 9961396.00, 1379302771.00]"] assert labels == expected def test_invalid_scheme(self): @@ -744,40 +1051,40 @@ # failing with matplotlib 1.4.3 (edge stays black even when specified) pytest.importorskip("matplotlib", "1.5.0") - from geopandas.plotting import plot_point_collection + from geopandas.plotting import _plot_point_collection, plot_point_collection from matplotlib.collections import PathCollection fig, ax = plt.subplots() - coll = plot_point_collection(ax, self.points) + coll = _plot_point_collection(ax, self.points) assert isinstance(coll, PathCollection) ax.cla() # default: single default matplotlib color - coll = plot_point_collection(ax, self.points) + coll = _plot_point_collection(ax, self.points) _check_colors(self.N, coll.get_facecolors(), [MPL_DFT_COLOR] * self.N) # edgecolor depends on matplotlib version # _check_colors(self.N, coll.get_edgecolors(), [MPL_DFT_COLOR]*self.N) ax.cla() # specify single other color - coll = plot_point_collection(ax, self.points, color="g") + coll = _plot_point_collection(ax, self.points, color="g") _check_colors(self.N, coll.get_facecolors(), ["g"] * self.N) _check_colors(self.N, coll.get_edgecolors(), ["g"] * self.N) ax.cla() # specify edgecolor/facecolor - coll = plot_point_collection(ax, self.points, facecolor="g", edgecolor="r") + coll = _plot_point_collection(ax, self.points, facecolor="g", edgecolor="r") _check_colors(self.N, coll.get_facecolors(), ["g"] * self.N) _check_colors(self.N, coll.get_edgecolors(), ["r"] * self.N) ax.cla() # list of colors - coll = plot_point_collection(ax, self.points, color=["r", "g", "b"]) + coll = _plot_point_collection(ax, self.points, color=["r", "g", "b"]) _check_colors(self.N, coll.get_facecolors(), ["r", "g", "b"]) _check_colors(self.N, coll.get_edgecolors(), ["r", "g", "b"]) ax.cla() - coll = plot_point_collection( + coll = _plot_point_collection( ax, self.points, color=[(0.5, 0.5, 0.5, 0.5), (0.1, 0.2, 0.3, 0.5), (0.4, 0.5, 0.6, 0.5)], @@ -795,15 +1102,19 @@ ax.cla() # not a color - with pytest.raises(TypeError): - plot_point_collection(ax, self.points, color="not color") + with pytest.raises((TypeError, ValueError)): + _plot_point_collection(ax, self.points, color="not color") + + # check DeprecationWarning + with pytest.warns(DeprecationWarning): + plot_point_collection(ax, self.points) def test_points_values(self): - from geopandas.plotting import plot_point_collection + from geopandas.plotting import _plot_point_collection # default colormap fig, ax = plt.subplots() - coll = plot_point_collection(ax, self.points, self.values) + coll = _plot_point_collection(ax, self.points, self.values) fig.canvas.draw_idle() cmap = plt.get_cmap() expected_colors = cmap(np.arange(self.N) / (self.N - 1)) @@ -812,36 +1123,39 @@ # _check_colors(self.N, coll.get_edgecolors(), expected_colors) def test_linestrings(self): - from geopandas.plotting import plot_linestring_collection + from geopandas.plotting import ( + _plot_linestring_collection, + plot_linestring_collection, + ) from matplotlib.collections import LineCollection fig, ax = plt.subplots() - coll = plot_linestring_collection(ax, self.lines) + coll = _plot_linestring_collection(ax, self.lines) assert isinstance(coll, LineCollection) ax.cla() # default: single default matplotlib color - coll = plot_linestring_collection(ax, self.lines) + coll = _plot_linestring_collection(ax, self.lines) _check_colors(self.N, coll.get_color(), [MPL_DFT_COLOR] * self.N) ax.cla() # specify single other color - coll = plot_linestring_collection(ax, self.lines, color="g") + coll = _plot_linestring_collection(ax, self.lines, color="g") _check_colors(self.N, coll.get_colors(), ["g"] * self.N) ax.cla() # specify edgecolor / facecolor - coll = plot_linestring_collection(ax, self.lines, facecolor="g", edgecolor="r") + coll = _plot_linestring_collection(ax, self.lines, facecolor="g", edgecolor="r") _check_colors(self.N, coll.get_facecolors(), ["g"] * self.N) _check_colors(self.N, coll.get_edgecolors(), ["r"] * self.N) ax.cla() # list of colors - coll = plot_linestring_collection(ax, self.lines, color=["r", "g", "b"]) + coll = _plot_linestring_collection(ax, self.lines, color=["r", "g", "b"]) _check_colors(self.N, coll.get_colors(), ["r", "g", "b"]) ax.cla() - coll = plot_linestring_collection( + coll = _plot_linestring_collection( ax, self.lines, color=[(0.5, 0.5, 0.5, 0.5), (0.1, 0.2, 0.3, 0.5), (0.4, 0.5, 0.6, 0.5)], @@ -854,7 +1168,7 @@ ax.cla() # pass through of kwargs - coll = plot_linestring_collection(ax, self.lines, linestyle="--", linewidth=1) + coll = _plot_linestring_collection(ax, self.lines, linestyle="--", linewidth=1) exp_ls = _style_to_linestring_onoffseq("dashed", 1) res_ls = coll.get_linestyle()[0] assert res_ls[0] == exp_ls[0] @@ -862,16 +1176,19 @@ ax.cla() # not a color - with pytest.raises(TypeError): - plot_linestring_collection(ax, self.lines, color="not color") + with pytest.raises((TypeError, ValueError)): + _plot_linestring_collection(ax, self.lines, color="not color") + # check DeprecationWarning + with pytest.warns(DeprecationWarning): + plot_linestring_collection(ax, self.lines) def test_linestrings_values(self): - from geopandas.plotting import plot_linestring_collection + from geopandas.plotting import _plot_linestring_collection fig, ax = plt.subplots() # default colormap - coll = plot_linestring_collection(ax, self.lines, self.values) + coll = _plot_linestring_collection(ax, self.lines, self.values) fig.canvas.draw_idle() cmap = plt.get_cmap() expected_colors = cmap(np.arange(self.N) / (self.N - 1)) @@ -879,7 +1196,7 @@ ax.cla() # specify colormap - coll = plot_linestring_collection(ax, self.lines, self.values, cmap="RdBu") + coll = _plot_linestring_collection(ax, self.lines, self.values, cmap="RdBu") fig.canvas.draw_idle() cmap = plt.get_cmap("RdBu") expected_colors = cmap(np.arange(self.N) / (self.N - 1)) @@ -887,7 +1204,7 @@ ax.cla() # specify vmin/vmax - coll = plot_linestring_collection(ax, self.lines, self.values, vmin=3, vmax=5) + coll = _plot_linestring_collection(ax, self.lines, self.values, vmin=3, vmax=5) fig.canvas.draw_idle() cmap = plt.get_cmap() expected_colors = cmap([0]) @@ -895,33 +1212,33 @@ ax.cla() def test_polygons(self): - from geopandas.plotting import plot_polygon_collection + from geopandas.plotting import _plot_polygon_collection, plot_polygon_collection from matplotlib.collections import PatchCollection fig, ax = plt.subplots() - coll = plot_polygon_collection(ax, self.polygons) + coll = _plot_polygon_collection(ax, self.polygons) assert isinstance(coll, PatchCollection) ax.cla() # default: single default matplotlib color - coll = plot_polygon_collection(ax, self.polygons) + coll = _plot_polygon_collection(ax, self.polygons) _check_colors(self.N, coll.get_facecolor(), [MPL_DFT_COLOR] * self.N) _check_colors(self.N, coll.get_edgecolor(), ["k"] * self.N) ax.cla() # default: color sets both facecolor and edgecolor - coll = plot_polygon_collection(ax, self.polygons, color="g") + coll = _plot_polygon_collection(ax, self.polygons, color="g") _check_colors(self.N, coll.get_facecolor(), ["g"] * self.N) _check_colors(self.N, coll.get_edgecolor(), ["g"] * self.N) ax.cla() # default: color can be passed as a list - coll = plot_polygon_collection(ax, self.polygons, color=["g", "b", "r"]) + coll = _plot_polygon_collection(ax, self.polygons, color=["g", "b", "r"]) _check_colors(self.N, coll.get_facecolor(), ["g", "b", "r"]) _check_colors(self.N, coll.get_edgecolor(), ["g", "b", "r"]) ax.cla() - coll = plot_polygon_collection( + coll = _plot_polygon_collection( ax, self.polygons, color=[(0.5, 0.5, 0.5, 0.5), (0.1, 0.2, 0.3, 0.5), (0.4, 0.5, 0.6, 0.5)], @@ -939,28 +1256,31 @@ ax.cla() # only setting facecolor keeps default for edgecolor - coll = plot_polygon_collection(ax, self.polygons, facecolor="g") + coll = _plot_polygon_collection(ax, self.polygons, facecolor="g") _check_colors(self.N, coll.get_facecolor(), ["g"] * self.N) _check_colors(self.N, coll.get_edgecolor(), ["k"] * self.N) ax.cla() # custom facecolor and edgecolor - coll = plot_polygon_collection(ax, self.polygons, facecolor="g", edgecolor="r") + coll = _plot_polygon_collection(ax, self.polygons, facecolor="g", edgecolor="r") _check_colors(self.N, coll.get_facecolor(), ["g"] * self.N) _check_colors(self.N, coll.get_edgecolor(), ["r"] * self.N) ax.cla() # not a color - with pytest.raises(TypeError): - plot_polygon_collection(ax, self.polygons, color="not color") + with pytest.raises((TypeError, ValueError)): + _plot_polygon_collection(ax, self.polygons, color="not color") + # check DeprecationWarning + with pytest.warns(DeprecationWarning): + plot_polygon_collection(ax, self.polygons) def test_polygons_values(self): - from geopandas.plotting import plot_polygon_collection + from geopandas.plotting import _plot_polygon_collection fig, ax = plt.subplots() # default colormap, edge is still black by default - coll = plot_polygon_collection(ax, self.polygons, self.values) + coll = _plot_polygon_collection(ax, self.polygons, self.values) fig.canvas.draw_idle() cmap = plt.get_cmap() exp_colors = cmap(np.arange(self.N) / (self.N - 1)) @@ -970,7 +1290,7 @@ ax.cla() # specify colormap - coll = plot_polygon_collection(ax, self.polygons, self.values, cmap="RdBu") + coll = _plot_polygon_collection(ax, self.polygons, self.values, cmap="RdBu") fig.canvas.draw_idle() cmap = plt.get_cmap("RdBu") exp_colors = cmap(np.arange(self.N) / (self.N - 1)) @@ -978,7 +1298,7 @@ ax.cla() # specify vmin/vmax - coll = plot_polygon_collection(ax, self.polygons, self.values, vmin=3, vmax=5) + coll = _plot_polygon_collection(ax, self.polygons, self.values, vmin=3, vmax=5) fig.canvas.draw_idle() cmap = plt.get_cmap() exp_colors = cmap([0]) @@ -986,7 +1306,7 @@ ax.cla() # override edgecolor - coll = plot_polygon_collection(ax, self.polygons, self.values, edgecolor="g") + coll = _plot_polygon_collection(ax, self.polygons, self.values, edgecolor="g") fig.canvas.draw_idle() cmap = plt.get_cmap() exp_colors = cmap(np.arange(self.N) / (self.N - 1)) @@ -1075,3 +1395,10 @@ """ offset, dashes = matplotlib.lines._get_dash_pattern(linestyle) return matplotlib.lines._scale_dashes(offset, dashes, linewidth) + + +def _style_to_vertices(markerstyle): + """ Converts a markerstyle string to a path. """ + # TODO: Vertices values are twice the actual path; unclear, why. + path = matplotlib.markers.MarkerStyle(markerstyle).get_path() + return path.vertices / 2 diff -Nru python-geopandas-0.7.0/geopandas/tests/test_show_versions.py python-geopandas-0.8.1/geopandas/tests/test_show_versions.py --- python-geopandas-0.7.0/geopandas/tests/test_show_versions.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/test_show_versions.py 2020-07-15 17:54:36.000000000 +0000 @@ -37,9 +37,9 @@ assert "pyproj" in deps_info assert "matplotlib" in deps_info assert "mapclassify" in deps_info - assert "pysal" in deps_info assert "geopy" in deps_info assert "psycopg2" in deps_info + assert "geoalchemy2" in deps_info def test_show_versions(capsys): diff -Nru python-geopandas-0.7.0/geopandas/tests/test_sindex.py python-geopandas-0.8.1/geopandas/tests/test_sindex.py --- python-geopandas-0.7.0/geopandas/tests/test_sindex.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/test_sindex.py 2020-07-15 17:54:36.000000000 +0000 @@ -1,19 +1,53 @@ import sys -from shapely.geometry import Point, Polygon +from shapely.geometry import ( + Point, + Polygon, + MultiPolygon, + box, + GeometryCollection, + LineString, +) +from numpy.testing import assert_array_equal import geopandas -from geopandas import GeoDataFrame, GeoSeries, base, read_file +from geopandas import _compat as compat +from geopandas import GeoDataFrame, GeoSeries, read_file, sindex, datasets import pytest +import numpy as np + + +class TestNoSindex: + @pytest.mark.skipif(sindex.has_sindex(), reason="Spatial index present, skipping") + def test_no_sindex_installed(self): + """Checks that an error is raised when no spatial index is present.""" + with pytest.raises(ImportError): + sindex.get_sindex_class() + + @pytest.mark.skipif( + compat.HAS_RTREE or not compat.HAS_PYGEOS, + reason="rtree cannot be disabled via flags", + ) + def test_no_sindex_active(self): + """Checks that an error is given when rtree is not installed + and compat.USE_PYGEOS is False. + """ + state = compat.USE_PYGEOS # try to save state + compat.set_use_pygeos(False) + with pytest.raises(ImportError): + sindex.get_sindex_class() + compat.set_use_pygeos(state) # try to restore state @pytest.mark.skipif(sys.platform.startswith("win"), reason="fails on AppVeyor") -@pytest.mark.skipif(not base.HAS_SINDEX, reason="Rtree absent, skipping") +@pytest.mark.skipif(not sindex.has_sindex(), reason="Spatial index absent, skipping") class TestSeriesSindex: def test_empty_geoseries(self): - - assert GeoSeries().sindex is None + """Tests creating a spatial index from an empty GeoSeries.""" + with pytest.warns(FutureWarning, match="Generated spatial index is empty"): + # TODO: add checking len(GeoSeries().sindex) == 0 once deprecated + assert not GeoSeries(dtype=object).sindex def test_point(self): s = GeoSeries([Point(0, 0)]) @@ -24,9 +58,13 @@ assert len(list(hits)) == 0 def test_empty_point(self): + """Tests that a single empty Point results in an empty tree.""" s = GeoSeries([Point()]) - assert s.sindex is None + with pytest.warns(FutureWarning, match="Generated spatial index is empty"): + # TODO: add checking len(s) == 0 once deprecated + assert not s.sindex + assert s._sindex_generated is True def test_polygons(self): @@ -54,7 +92,7 @@ @pytest.mark.skipif(sys.platform.startswith("win"), reason="fails on AppVeyor") -@pytest.mark.skipif(not base.HAS_SINDEX, reason="Rtree absent, skipping") +@pytest.mark.skipif(not sindex.has_sindex(), reason="Spatial index absent, skipping") class TestFrameSindex: def setup_method(self): data = { @@ -67,7 +105,9 @@ def test_sindex(self): self.df.crs = "epsg:4326" assert self.df.sindex.size == 5 - hits = list(self.df.sindex.intersection((2.5, 2.5, 4, 4), objects=True)) + with pytest.warns(FutureWarning, match="`objects` is deprecated"): + # TODO: remove warning check once deprecated + hits = list(self.df.sindex.intersection((2.5, 2.5, 4, 4), objects=True)) assert len(hits) == 2 assert hits[0].object == 3 @@ -95,21 +135,27 @@ def test_merge_geo(self): # First check that we gets hits from the boros frame. tree = self.boros.sindex - hits = tree.intersection((1012821.80, 229228.26), objects=True) + with pytest.warns(FutureWarning, match="`objects` is deprecated"): + # TODO: remove warning check once deprecated + hits = tree.intersection((1012821.80, 229228.26), objects=True) res = [self.boros.loc[hit.object]["BoroName"] for hit in hits] assert res == ["Bronx", "Queens"] # Check that we only get the Bronx from this view. first = self.boros[self.boros["BoroCode"] < 3] tree = first.sindex - hits = tree.intersection((1012821.80, 229228.26), objects=True) + with pytest.warns(FutureWarning, match="`objects` is deprecated"): + # TODO: remove warning check once deprecated + hits = tree.intersection((1012821.80, 229228.26), objects=True) res = [first.loc[hit.object]["BoroName"] for hit in hits] assert res == ["Bronx"] # Check that we only get Queens from this view. second = self.boros[self.boros["BoroCode"] >= 3] tree = second.sindex - hits = tree.intersection((1012821.80, 229228.26), objects=True) + with pytest.warns(FutureWarning, match="`objects` is deprecated"): + # TODO: remove warning check once deprecated + hits = tree.intersection((1012821.80, 229228.26), objects=True) res = ([second.loc[hit.object]["BoroName"] for hit in hits],) assert res == ["Queens"] @@ -118,6 +164,402 @@ assert len(merged) == 5 assert merged.sindex.size == 5 tree = merged.sindex - hits = tree.intersection((1012821.80, 229228.26), objects=True) + with pytest.warns(FutureWarning, match="`objects` is deprecated"): + # TODO: remove warning check once deprecated + hits = tree.intersection((1012821.80, 229228.26), objects=True) res = [merged.loc[hit.object]["BoroName"] for hit in hits] assert res == ["Bronx", "Queens"] + + +@pytest.mark.skipif(not sindex.has_sindex(), reason="Spatial index absent, skipping") +class TestPygeosInterface: + def setup_method(self): + data = { + "location": [Point(x, y) for x, y in zip(range(5), range(5))] + + [box(10, 10, 20, 20)] # include a box geometry + } + self.df = GeoDataFrame(data, geometry="location") + self.expected_size = len(data["location"]) + + # --------------------------- `intersection` tests -------------------------- # + @pytest.mark.parametrize( + "test_geom, expected", + ( + ((-1, -1, -0.5, -0.5), []), + ((-0.5, -0.5, 0.5, 0.5), [0]), + ((0, 0, 1, 1), [0, 1]), + ((0, 0), [0]), + ), + ) + def test_intersection_bounds_tuple(self, test_geom, expected): + """Tests the `intersection` method with valid inputs.""" + res = list(self.df.sindex.intersection(test_geom)) + assert_array_equal(res, expected) + + @pytest.mark.parametrize("test_geom", ((-1, -1, -0.5), -0.5, None, Point(0, 0))) + def test_intersection_invalid_bounds_tuple(self, test_geom): + """Tests the `intersection` method with invalid inputs.""" + if compat.USE_PYGEOS: + with pytest.raises(TypeError): + # we raise a useful TypeError + self.df.sindex.intersection(test_geom) + else: + with pytest.raises((TypeError, Exception)): + # catch a general exception + # rtree raises an RTreeError which we need to catch + self.df.sindex.intersection(test_geom) + + # ------------------------------ `query` tests ------------------------------ # + @pytest.mark.parametrize( + "predicate, test_geom, expected", + ( + (None, box(-1, -1, -0.5, -0.5), []), # bbox does not intersect + (None, box(-0.5, -0.5, 0.5, 0.5), [0]), # bbox intersects + (None, box(0, 0, 1, 1), [0, 1]), # bbox intersects multiple + ( + None, + LineString([(0, 1), (1, 0)]), + [0, 1], + ), # bbox intersects but not geometry + ("intersects", box(-1, -1, -0.5, -0.5), []), # bbox does not intersect + ( + "intersects", + box(-0.5, -0.5, 0.5, 0.5), + [0], + ), # bbox and geometry intersect + ( + "intersects", + box(0, 0, 1, 1), + [0, 1], + ), # bbox and geometry intersect multiple + ( + "intersects", + LineString([(0, 1), (1, 0)]), + [], + ), # bbox intersects but not geometry + ("within", box(0.25, 0.28, 0.75, 0.75), []), # does not intersect + ("within", box(0, 0, 10, 10), []), # intersects but is not within + ("within", box(11, 11, 12, 12), [5]), # intersects and is within + ("within", LineString([(0, 1), (1, 0)]), []), # intersects but not within + ("contains", box(0, 0, 1, 1), []), # intersects but does not contain + ("contains", box(0, 0, 1.001, 1.001), [1]), # intersects and contains + ("contains", box(0.5, 0.5, 1.5, 1.5), [1]), # intersects and contains + ("contains", box(-1, -1, 2, 2), [0, 1]), # intersects and contains multiple + ( + "contains", + LineString([(0, 1), (1, 0)]), + [], + ), # intersects but not contains + ("touches", box(-1, -1, 0, 0), [0]), # bbox intersects and touches + ( + "touches", + box(-0.5, -0.5, 1.5, 1.5), + [], + ), # bbox intersects but geom does not touch + ), + ) + def test_query(self, predicate, test_geom, expected): + """Tests the `query` method with valid inputs and valid predicates.""" + res = self.df.sindex.query(test_geom, predicate=predicate) + assert_array_equal(res, expected) + + def test_query_invalid_geometry(self): + """Tests the `query` method with invalid geometry. + """ + with pytest.raises(TypeError): + self.df.sindex.query("notavalidgeom") + + @pytest.mark.parametrize( + "test_geom, expected_value", + [ + (None, []), + (GeometryCollection(), []), + (Point(), []), + (MultiPolygon(), []), + (Polygon(), []), + ], + ) + def test_query_empty_geometry(self, test_geom, expected_value): + """Tests the `query` method with empty geometry. + """ + res = self.df.sindex.query(test_geom) + assert_array_equal(res, expected_value) + + def test_query_invalid_predicate(self): + """Tests the `query` method with invalid predicates. + """ + test_geom = box(-1, -1, -0.5, -0.5) + with pytest.raises(ValueError): + self.df.sindex.query(test_geom, predicate="test") + + @pytest.mark.parametrize( + "sort, expected", + ( + (True, [[0, 0, 0], [0, 1, 2]]), + # False could be anything, at least we'll know if it changes + (False, [[0, 0, 0], [0, 1, 2]]), + ), + ) + def test_query_sorting(self, sort, expected): + """Check that results from `query` don't depend on the + order of geometries. + """ + # these geometries come from a reported issue: + # https://github.com/geopandas/geopandas/issues/1337 + # there is no theoretical reason they were chosen + test_polys = GeoSeries([Polygon([(1, 1), (3, 1), (3, 3), (1, 3)])]) + tree_polys = GeoSeries( + [ + Polygon([(1, 1), (3, 1), (3, 3), (1, 3)]), + Polygon([(-1, 1), (1, 1), (1, 3), (-1, 3)]), + Polygon([(3, 3), (5, 3), (5, 5), (3, 5)]), + ] + ) + expected = [0, 1, 2] + + # pass through GeoSeries to have GeoPandas + # determine if it should use shapely or pygeos geometry objects + tree_df = geopandas.GeoDataFrame(geometry=tree_polys) + test_df = geopandas.GeoDataFrame(geometry=test_polys) + + test_geo = test_df.geometry.values.data[0] + res = tree_df.sindex.query(test_geo, sort=sort) + try: + assert_array_equal(res, expected) + except AssertionError as e: + if not compat.USE_PYGEOS and sort is False: + pytest.xfail( + "rtree results are known to be unordered, see " + "https://github.com/geopandas/geopandas/issues/1337\n" + "Expected:\n {}\n".format(expected) + + "Got:\n {}\n".format(res.tolist()) + ) + raise e + + # ------------------------- `query_bulk` tests -------------------------- # + @pytest.mark.parametrize( + "predicate, test_geom, expected", + ( + (None, [(-1, -1, -0.5, -0.5)], [[], []]), + (None, [(-0.5, -0.5, 0.5, 0.5)], [[0], [0]]), + (None, [(0, 0, 1, 1)], [[0, 0], [0, 1]]), + ("intersects", [(-1, -1, -0.5, -0.5)], [[], []]), + ("intersects", [(-0.5, -0.5, 0.5, 0.5)], [[0], [0]]), + ("intersects", [(0, 0, 1, 1)], [[0, 0], [0, 1]]), + # only second geom intersects + ("intersects", [(-1, -1, -0.5, -0.5), (-0.5, -0.5, 0.5, 0.5)], [[1], [0]]), + # both geoms intersect + ( + "intersects", + [(-1, -1, 1, 1), (-0.5, -0.5, 0.5, 0.5)], + [[0, 0, 1], [0, 1, 0]], + ), + ("within", [(0.25, 0.28, 0.75, 0.75)], [[], []]), # does not intersect + ("within", [(0, 0, 10, 10)], [[], []]), # intersects but is not within + ("within", [(11, 11, 12, 12)], [[0], [5]]), # intersects and is within + ("contains", [(0, 0, 1, 1)], [[], []]), # intersects but does not contain + ( + "contains", + [(0, 0, 1.001, 1.001)], + [[0], [1]], + ), # intersects 2 and contains 1 + ( + "contains", + [(0.5, 0.5, 1.001, 1.001)], + [[0], [1]], + ), # intersects 1 and contains 1 + ("contains", [(0.5, 0.5, 1.5, 1.5)], [[0], [1]]), # intersects and contains + ( + "contains", + [(-1, -1, 2, 2)], + [[0, 0], [0, 1]], + ), # intersects and contains multiple + ), + ) + def test_query_bulk(self, predicate, test_geom, expected): + """Tests the `query_bulk` method with valid + inputs and valid predicates. + """ + # pass through GeoSeries to have GeoPandas + # determine if it should use shapely or pygeos geometry objects + test_geom = geopandas.GeoSeries( + [box(*geom) for geom in test_geom], index=range(len(test_geom)) + ) + res = self.df.sindex.query_bulk(test_geom, predicate=predicate) + assert_array_equal(res, expected) + + @pytest.mark.parametrize( + "test_geoms, expected_value", + [ + # single empty geometry + ([GeometryCollection()], [[], []]), + # None should be skipped + ([GeometryCollection(), None], [[], []]), + ([None], [[], []]), + ([None, box(-0.5, -0.5, 0.5, 0.5), None], [[1], [0]]), + ], + ) + def test_query_bulk_empty_geometry(self, test_geoms, expected_value): + """Tests the `query_bulk` method with an empty geometry. + """ + # pass through GeoSeries to have GeoPandas + # determine if it should use shapely or pygeos geometry objects + # note: for this test, test_geoms (note plural) is a list already + test_geoms = geopandas.GeoSeries(test_geoms, index=range(len(test_geoms))) + res = self.df.sindex.query_bulk(test_geoms) + assert_array_equal(res, expected_value) + + def test_query_bulk_empty_input_array(self): + """Tests the `query_bulk` method with an empty input array. + """ + test_array = np.array([], dtype=object) + expected_value = [[], []] + res = self.df.sindex.query_bulk(test_array) + assert_array_equal(res, expected_value) + + def test_query_bulk_invalid_input_geometry(self): + """Tests the `query_bulk` method with invalid input for the `geometry` parameter. + """ + test_array = "notanarray" + with pytest.raises(TypeError): + self.df.sindex.query_bulk(test_array) + + def test_query_bulk_invalid_predicate(self): + """Tests the `query_bulk` method with invalid predicates. + """ + test_geom_bounds = (-1, -1, -0.5, -0.5) + test_predicate = "test" + + # pass through GeoSeries to have GeoPandas + # determine if it should use shapely or pygeos geometry objects + test_geom = geopandas.GeoSeries([box(*test_geom_bounds)], index=["0"]) + + with pytest.raises(ValueError): + self.df.sindex.query_bulk(test_geom.geometry, predicate=test_predicate) + + @pytest.mark.parametrize( + "predicate, test_geom, expected", + ( + (None, (-1, -1, -0.5, -0.5), [[], []]), + ("intersects", (-1, -1, -0.5, -0.5), [[], []]), + ("contains", (-1, -1, 1, 1), [[0], [0]]), + ), + ) + def test_query_bulk_input_type(self, predicate, test_geom, expected): + """Tests that query_bulk can accept a GeoSeries, GeometryArray or + numpy array. + """ + # pass through GeoSeries to have GeoPandas + # determine if it should use shapely or pygeos geometry objects + test_geom = geopandas.GeoSeries([box(*test_geom)], index=["0"]) + + # test GeoSeries + res = self.df.sindex.query_bulk(test_geom, predicate=predicate) + assert_array_equal(res, expected) + + # test GeometryArray + res = self.df.sindex.query_bulk(test_geom.geometry, predicate=predicate) + assert_array_equal(res, expected) + res = self.df.sindex.query_bulk(test_geom.geometry.values, predicate=predicate) + assert_array_equal(res, expected) + + # test numpy array + res = self.df.sindex.query_bulk( + test_geom.geometry.values.data, predicate=predicate + ) + assert_array_equal(res, expected) + res = self.df.sindex.query_bulk( + test_geom.geometry.values.data, predicate=predicate + ) + assert_array_equal(res, expected) + + @pytest.mark.parametrize( + "sort, expected", + ( + (True, [[0, 0, 0], [0, 1, 2]]), + # False could be anything, at least we'll know if it changes + (False, [[0, 0, 0], [0, 1, 2]]), + ), + ) + def test_query_bulk_sorting(self, sort, expected): + """Check that results from `query_bulk` don't depend + on the order of geometries. + """ + # these geometries come from a reported issue: + # https://github.com/geopandas/geopandas/issues/1337 + # there is no theoretical reason they were chosen + test_polys = GeoSeries([Polygon([(1, 1), (3, 1), (3, 3), (1, 3)])]) + tree_polys = GeoSeries( + [ + Polygon([(1, 1), (3, 1), (3, 3), (1, 3)]), + Polygon([(-1, 1), (1, 1), (1, 3), (-1, 3)]), + Polygon([(3, 3), (5, 3), (5, 5), (3, 5)]), + ] + ) + + # pass through GeoSeries to have GeoPandas + # determine if it should use shapely or pygeos geometry objects + tree_df = geopandas.GeoDataFrame(geometry=tree_polys) + test_df = geopandas.GeoDataFrame(geometry=test_polys) + + res = tree_df.sindex.query_bulk(test_df.geometry, sort=sort) + try: + assert_array_equal(res, expected) + except AssertionError as e: + if not compat.USE_PYGEOS and sort is False: + pytest.xfail( + "rtree results are known to be unordered, see " + "https://github.com/geopandas/geopandas/issues/1337\n" + "Expected:\n {}\n".format(expected) + + "Got:\n {}\n".format(res.tolist()) + ) + raise e + + # --------------------------- misc tests ---------------------------- # + + def test_empty_tree_geometries(self): + """Tests building sindex with interleaved empty geometries. + """ + geoms = [Point(0, 0), None, Point(), Point(1, 1), Point()] + df = geopandas.GeoDataFrame(geometry=geoms) + assert df.sindex.query(Point(1, 1))[0] == 3 + + def test_size(self): + """Tests the `size` property.""" + assert self.df.sindex.size == self.expected_size + + def test_len(self): + """Tests the `__len__` method of spatial indexes.""" + assert len(self.df.sindex) == self.expected_size + + def test_is_empty(self): + """Tests the `is_empty` property.""" + # create empty tree + cls_ = sindex.get_sindex_class() + empty = geopandas.GeoSeries(dtype=object) + tree = cls_(empty) + assert tree.is_empty + # create a non-empty tree + non_empty = geopandas.GeoSeries([Point(0, 0)]) + tree = cls_(non_empty) + assert not tree.is_empty + + @pytest.mark.parametrize( + "predicate, expected_shape", + [ + (None, (2, 396)), + ("intersects", (2, 172)), + ("within", (2, 172)), + ("contains", (2, 0)), + ("overlaps", (2, 0)), + ("crosses", (2, 0)), + ("touches", (2, 0)), + ], + ) + def test_integration_natural_earth(self, predicate, expected_shape): + """Tests output sizes for the naturalearth datasets.""" + world = read_file(datasets.get_path("naturalearth_lowres")) + capitals = read_file(datasets.get_path("naturalearth_cities")) + + res = world.sindex.query_bulk(capitals.geometry, predicate) + assert res.shape == expected_shape diff -Nru python-geopandas-0.7.0/geopandas/tests/test_testing.py python-geopandas-0.8.1/geopandas/tests/test_testing.py --- python-geopandas-0.7.0/geopandas/tests/test_testing.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/test_testing.py 2020-07-15 17:54:36.000000000 +0000 @@ -1,8 +1,10 @@ import numpy as np from shapely.geometry import Point, Polygon +from pandas import Series from geopandas import GeoDataFrame, GeoSeries +from geopandas.array import from_shapely from geopandas.testing import assert_geodataframe_equal, assert_geoseries_equal import pytest @@ -20,12 +22,46 @@ ] ) + +s3 = Series( + [ + Polygon([(0, 2), (0, 0), (2, 0), (2, 2)]), + Polygon([(2, 2), (4, 2), (4, 4), (2, 4)]), + ] +) + +a = from_shapely( + [ + Polygon([(0, 2), (0, 0), (2, 0), (2, 2)]), + Polygon([(2, 2), (4, 2), (4, 4), (2, 4)]), + ] +) + +s4 = Series(a) + df1 = GeoDataFrame({"col1": [1, 2], "geometry": s1}) df2 = GeoDataFrame({"col1": [1, 2], "geometry": s2}) +s4 = s1.copy() +s4.crs = 4326 +s5 = s2.copy() +s5.crs = 27700 +df4 = GeoDataFrame( + {"col1": [1, 2], "geometry": s1.copy(), "geom2": s4.copy(), "geom3": s5.copy()}, + crs=3857, +) +df5 = GeoDataFrame( + {"col1": [1, 2], "geometry": s1.copy(), "geom3": s5.copy(), "geom2": s4.copy()}, + crs=3857, +) + +@pytest.mark.filterwarnings("ignore::UserWarning") def test_geoseries(): assert_geoseries_equal(s1, s2) + assert_geoseries_equal(s1, s3, check_series_type=False, check_dtype=False) + assert_geoseries_equal(s3, s2, check_series_type=False, check_dtype=False) + assert_geoseries_equal(s1, s4, check_series_type=False) with pytest.raises(AssertionError): assert_geoseries_equal(s1, s2, check_less_precise=True) @@ -47,6 +83,11 @@ with pytest.raises(AssertionError): assert_geodataframe_equal(df1, df3) + assert_geodataframe_equal(df5, df4, check_like=True) + df5.geom2.crs = 3857 + with pytest.raises(AssertionError): + assert_geodataframe_equal(df5, df4, check_like=True) + def test_equal_nans(): s = GeoSeries([Point(0, 0), np.nan]) @@ -61,7 +102,7 @@ def test_ignore_crs_mismatch(): - df1 = GeoDataFrame({"col1": [1, 2], "geometry": s1}, crs="EPSG:4326") + df1 = GeoDataFrame({"col1": [1, 2], "geometry": s1.copy()}, crs="EPSG:4326") df2 = GeoDataFrame({"col1": [1, 2], "geometry": s1}, crs="EPSG:31370") with pytest.raises(AssertionError): diff -Nru python-geopandas-0.7.0/geopandas/tests/util.py python-geopandas-0.8.1/geopandas/tests/util.py --- python-geopandas-0.7.0/geopandas/tests/util.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tests/util.py 2020-07-15 17:54:36.000000000 +0000 @@ -1,5 +1,4 @@ import os.path -import sqlite3 from pandas import Series @@ -15,15 +14,6 @@ PACKAGE_DIR = os.path.dirname(os.path.dirname(HERE)) -try: - import psycopg2 - from psycopg2 import OperationalError -except ImportError: - - class OperationalError(Exception): - pass - - # mock not used here, but the import from here is used in other modules try: import unittest.mock as mock # noqa @@ -47,26 +37,6 @@ assert Series(df.geometry.type).dropna().eq("MultiPolygon").all() -def connect(dbname, user=None, password=None, host=None, port=None): - """ - Initiaties a connection to a postGIS database that must already exist. - See create_postgis for more information. - """ - - user = user or os.environ.get("PGUSER") - password = password or os.environ.get("PGPASSWORD") - host = host or os.environ.get("PGHOST") - port = port or os.environ.get("PGPORT") - try: - con = psycopg2.connect( - dbname=dbname, user=user, password=password, host=host, port=port - ) - except (NameError, OperationalError): - return None - - return con - - def get_srid(df): """Return srid from `df.crs`.""" if df.crs is not None: @@ -74,32 +44,6 @@ return 0 -def connect_spatialite(): - """ - Return a memory-based SQLite3 connection with SpatiaLite enabled & initialized. - - `The sqlite3 module must be built with loadable extension support - `_ and - `SpatiaLite `_ - must be available on the system as a SQLite module. - Packages available on Anaconda meet requirements. - - Exceptions - ---------- - ``AttributeError`` on missing support for loadable SQLite extensions - ``sqlite3.OperationalError`` on missing SpatiaLite - """ - try: - with sqlite3.connect(":memory:") as con: - con.enable_load_extension(True) - con.load_extension("mod_spatialite") - con.execute("SELECT InitSpatialMetaData(TRUE)") - except Exception: - con.close() - raise - return con - - def create_spatialite(con, df): """ Return a SpatiaLite connection containing the nybb table. @@ -143,10 +87,9 @@ for row in df.itertuples(index=False) ), ) - return con -def create_postgis(df, srid=None, geom_col="geom"): +def create_postgis(con, df, srid=None, geom_col="geom"): """ Create a nybb table in the test_geopandas PostGIS database. Returns a boolean indicating whether the database table was successfully @@ -158,10 +101,6 @@ # 'test_geopandas' and enable postgis in it: # > createdb test_geopandas # > psql -c "CREATE EXTENSION postgis" -d test_geopandas - con = connect("test_geopandas") - if con is None: - return False - if srid is not None: geom_schema = "geometry(MULTIPOLYGON, {})".format(srid) geom_insert = "ST_SetSRID(ST_GeometryFromText(%s), {})".format(srid) @@ -201,6 +140,3 @@ finally: cursor.close() con.commit() - con.close() - - return True diff -Nru python-geopandas-0.7.0/geopandas/tools/clip.py python-geopandas-0.8.1/geopandas/tools/clip.py --- python-geopandas-0.7.0/geopandas/tools/clip.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tools/clip.py 2020-07-15 17:54:36.000000000 +0000 @@ -13,6 +13,7 @@ from shapely.geometry import Polygon, MultiPolygon from geopandas import GeoDataFrame, GeoSeries +from geopandas.array import _check_crs, _crs_mismatch_warn def _clip_points(gdf, poly): @@ -36,12 +37,7 @@ The returned GeoDataFrame is a subset of gdf that intersects with poly. """ - spatial_index = gdf.sindex - bbox = poly.bounds - sidx = list(spatial_index.intersection(bbox)) - gdf_sub = gdf.iloc[sidx] - - return gdf_sub[gdf_sub.geometry.intersects(poly)] + return gdf.iloc[gdf.sindex.query(poly, predicate="intersects")] def _clip_line_poly(gdf, poly): @@ -65,26 +61,17 @@ The returned GeoDataFrame is a clipped subset of gdf that intersects with poly. """ - spatial_index = gdf.sindex - - # Create a box for the initial intersection - bbox = poly.bounds - # Get a list of id's for each object that overlaps the bounding box and - # subset the data to just those lines - sidx = list(spatial_index.intersection(bbox)) - gdf_sub = gdf.iloc[sidx] + gdf_sub = gdf.iloc[gdf.sindex.query(poly, predicate="intersects")] # Clip the data with the polygon if isinstance(gdf_sub, GeoDataFrame): clipped = gdf_sub.copy() clipped["geometry"] = gdf_sub.intersection(poly) - - # Return the clipped layer with no null geometry values or empty geometries - return clipped[~clipped.geometry.is_empty & clipped.geometry.notnull()] else: # GeoSeries clipped = gdf_sub.intersection(poly) - return clipped[~clipped.is_empty & clipped.notnull()] + + return clipped def clip(gdf, mask, keep_geom_type=False): @@ -144,6 +131,10 @@ ) if isinstance(mask, (GeoDataFrame, GeoSeries)): + if not _check_crs(gdf, mask): + _crs_mismatch_warn(gdf, mask, stacklevel=3) + + if isinstance(mask, (GeoDataFrame, GeoSeries)): box_mask = mask.total_bounds else: box_mask = mask.bounds @@ -152,7 +143,7 @@ ((box_mask[0] <= box_gdf[2]) and (box_gdf[0] <= box_mask[2])) and ((box_mask[1] <= box_gdf[3]) and (box_gdf[1] <= box_mask[3])) ): - return GeoDataFrame(columns=gdf.columns, crs=gdf.crs) + return gdf.iloc[:0] if isinstance(mask, (GeoDataFrame, GeoSeries)): poly = mask.geometry.unary_union @@ -242,7 +233,11 @@ elif orig_type in lines: concat = concat.loc[concat.geom_type.isin(lines)] - # preserve the original order of the input + # Return empty GeoDataFrame or GeoSeries if no shapes remain + if len(concat) == 0: + return gdf.iloc[:0] + + # Preserve the original order of the input if isinstance(concat, GeoDataFrame): concat["_order"] = order return concat.sort_values(by="_order").drop(columns="_order") diff -Nru python-geopandas-0.7.0/geopandas/tools/overlay.py python-geopandas-0.8.1/geopandas/tools/overlay.py --- python-geopandas-0.7.0/geopandas/tools/overlay.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tools/overlay.py 2020-07-15 17:54:36.000000000 +0000 @@ -1,9 +1,11 @@ +import warnings from functools import reduce import numpy as np import pandas as pd from geopandas import GeoDataFrame, GeoSeries +from geopandas.array import _check_crs, _crs_mismatch_warn def _ensure_geometry_column(df): @@ -25,27 +27,20 @@ Overlay Intersection operation used in overlay function """ # Spatial Index to create intersections - spatial_index = df2.sindex - bbox = df1.geometry.apply(lambda x: x.bounds) - sidx = bbox.apply(lambda x: list(spatial_index.intersection(x))) + idx1, idx2 = df2.sindex.query_bulk(df1.geometry, predicate="intersects", sort=True) # Create pairs of geometries in both dataframes to be intersected - nei = [] - for i, j in enumerate(sidx): - for k in j: - nei.append([i, k]) - if nei != []: - pairs = pd.DataFrame(nei, columns=["__idx1", "__idx2"]) - left = df1.geometry.take(pairs["__idx1"].values) + if idx1.size > 0 and idx2.size > 0: + left = df1.geometry.take(idx1) left.reset_index(drop=True, inplace=True) - right = df2.geometry.take(pairs["__idx2"].values) + right = df2.geometry.take(idx2) right.reset_index(drop=True, inplace=True) intersections = left.intersection(right) poly_ix = intersections.type.isin(["Polygon", "MultiPolygon"]) intersections.loc[poly_ix] = intersections[poly_ix].buffer(0) # only keep actual intersecting geometries - pairs_intersect = pairs[~intersections.is_empty] - geom_intersect = intersections[~intersections.is_empty] + pairs_intersect = pd.DataFrame({"__idx1": idx1, "__idx2": idx2}) + geom_intersect = intersections # merge data for intersecting geometries df1 = df1.reset_index(drop=True) @@ -59,7 +54,7 @@ df2.drop(df2._geometry_column_name, axis=1), left_on="__idx2", right_index=True, - suffixes=["_1", "_2"], + suffixes=("_1", "_2"), ) return GeoDataFrame(dfinter, geometry=geom_intersect, crs=df1.crs) @@ -75,10 +70,14 @@ """ Overlay Difference operation used in overlay function """ - # Spatial Index to create intersections - spatial_index = df2.sindex - bbox = df1.geometry.apply(lambda x: x.bounds) - sidx = bbox.apply(lambda x: list(spatial_index.intersection(x))) + # spatial index query to find intersections + idx1, idx2 = df2.sindex.query_bulk(df1.geometry, predicate="intersects", sort=True) + idx1_unique, idx1_unique_indices = np.unique(idx1, return_index=True) + idx2_split = np.split(idx2, idx1_unique_indices[1:]) + sidx = [ + idx2_split.pop(0) if idx in idx1_unique else [] + for idx in range(df1.geometry.size) + ] # Create differences new_g = [] for geom, neighbours in zip(df1.geometry, sidx): @@ -86,7 +85,7 @@ lambda x, y: x.difference(y), [geom] + list(df2.geometry.iloc[neighbours]) ) new_g.append(new) - differences = GeoSeries(new_g, index=df1.index) + differences = GeoSeries(new_g, index=df1.index, crs=df1.crs) poly_ix = differences.type.isin(["Polygon", "MultiPolygon"]) differences.loc[poly_ix] = differences[poly_ix].buffer(0) geom_diff = differences[~differences.is_empty].copy() @@ -110,7 +109,7 @@ _ensure_geometry_column(dfdiff2) # combine both 'difference' dataframes dfsym = dfdiff1.merge( - dfdiff2, on=["__idx1", "__idx2"], how="outer", suffixes=["_1", "_2"] + dfdiff2, on=["__idx1", "__idx2"], how="outer", suffixes=("_1", "_2") ) geometry = dfsym.geometry_1.copy() geometry.name = "geometry" @@ -139,16 +138,17 @@ def overlay(df1, df2, how="intersection", make_valid=True, keep_geom_type=True): - """Perform spatial overlay between two polygons. + """Perform spatial overlay between two GeoDataFrames. - Currently only supports data GeoDataFrames with polygons. - Implements several methods that are all effectively subsets of - the union. + Currently only supports data GeoDataFrames with uniform geometry types, + i.e. containing only (Multi)Polygons, or only (Multi)Points, or a + combination of (Multi)LineString and LinearRing shapes. + Implements several methods that are all effectively subsets of the union. Parameters ---------- - df1 : GeoDataFrame with MultiPolygon or Polygon geometry column - df2 : GeoDataFrame with MultiPolygon or Polygon geometry column + df1 : GeoDataFrame + df2 : GeoDataFrame how : string Method of spatial overlay: 'intersection', 'union', 'identity', 'symmetric_difference' or 'difference'. @@ -174,13 +174,17 @@ # Error Messages if how not in allowed_hows: raise ValueError( - "`how` was '{0}' but is expected to be in %s".format(how, allowed_hows) + "`how` was '{0}' but is expected to be in {1}".format(how, allowed_hows) ) if isinstance(df1, GeoSeries) or isinstance(df2, GeoSeries): raise NotImplementedError( "overlay currently only implemented for " "GeoDataFrames" ) + + if not _check_crs(df1, df2): + _crs_mismatch_warn(df1, df2, stacklevel=3) + polys = ["Polygon", "MultiPolygon"] lines = ["LineString", "MultiLineString", "LinearRing"] points = ["Point", "MultiPoint"] @@ -201,17 +205,19 @@ if df2.geom_type.isin(polys).all(): df2[df2._geometry_column_name] = df2.geometry.buffer(0) - if how == "difference": - return _overlay_difference(df1, df2) - elif how == "intersection": - result = _overlay_intersection(df1, df2) - elif how == "symmetric_difference": - result = _overlay_symmetric_diff(df1, df2) - elif how == "union": - result = _overlay_union(df1, df2) - elif how == "identity": - dfunion = _overlay_union(df1, df2) - result = dfunion[dfunion["__idx1"].notnull()].copy() + with warnings.catch_warnings(): # CRS checked above, supress array-level warning + warnings.filterwarnings("ignore", message="CRS mismatch between the CRS") + if how == "difference": + return _overlay_difference(df1, df2) + elif how == "intersection": + result = _overlay_intersection(df1, df2) + elif how == "symmetric_difference": + result = _overlay_symmetric_diff(df1, df2) + elif how == "union": + result = _overlay_union(df1, df2) + elif how == "identity": + dfunion = _overlay_union(df1, df2) + result = dfunion[dfunion["__idx1"].notnull()].copy() if keep_geom_type: type = df1.geom_type.iloc[0] diff -Nru python-geopandas-0.7.0/geopandas/tools/_show_versions.py python-geopandas-0.8.1/geopandas/tools/_show_versions.py --- python-geopandas-0.7.0/geopandas/tools/_show_versions.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tools/_show_versions.py 2020-07-15 17:54:36.000000000 +0000 @@ -94,9 +94,10 @@ "pyproj", "matplotlib", "mapclassify", - "pysal", "geopy", "psycopg2", + "geoalchemy2", + "pyarrow", ] def get_version(module): @@ -122,9 +123,12 @@ """ Print system information and installed module versions. - Example - ------- - > python -c "import geopandas; geopandas.show_versions()" + Examples + -------- + + :: + + $ python -c "import geopandas; geopandas.show_versions()" """ sys_info = _get_sys_info() deps_info = _get_deps_info() diff -Nru python-geopandas-0.7.0/geopandas/tools/sjoin.py python-geopandas-0.8.1/geopandas/tools/sjoin.py --- python-geopandas-0.7.0/geopandas/tools/sjoin.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tools/sjoin.py 2020-07-15 17:54:36.000000000 +0000 @@ -1,11 +1,9 @@ -from warnings import warn +import warnings -import numpy as np import pandas as pd -from shapely import prepared - from geopandas import GeoDataFrame +from geopandas.array import _check_crs, _crs_mismatch_warn def sjoin( @@ -23,7 +21,7 @@ * 'right': use keys from right_df; retain only right_df geometry column * 'inner': use intersection of keys from both dfs; retain only left_df geometry column - op : string, default 'intersection' + op : string, default 'intersects' Binary predicate, one of {'intersects', 'contains', 'within'}. See http://shapely.readthedocs.io/en/latest/manual.html#binary-predicates. lsuffix : string, default 'left' @@ -54,13 +52,8 @@ '`op` was "%s" but is expected to be in %s' % (op, allowed_ops) ) - if left_df.crs != right_df.crs: - warn( - ( - "CRS of frames being joined does not match!" - "(%s != %s)" % (left_df.crs, right_df.crs) - ) - ) + if not _check_crs(left_df, right_df): + _crs_mismatch_warn(left_df, right_df, stacklevel=3) index_left = "index_%s" % lsuffix index_right = "index_%s" % rsuffix @@ -74,18 +67,41 @@ " joined".format(index_left, index_right) ) - # Attempt to re-use spatial indexes, otherwise generate the spatial index - # for the longer dataframe - if right_df._sindex_generated or ( - not left_df._sindex_generated and right_df.shape[0] > left_df.shape[0] - ): - tree_idx = right_df.sindex - tree_idx_right = True + # query index + with warnings.catch_warnings(): + # We don't need to show our own warning here + # TODO remove this once the deprecation has been enforced + warnings.filterwarnings( + "ignore", "Generated spatial index is empty", FutureWarning + ) + if op == "within": + # within is implemented as the inverse of contains + # contains is a faster predicate + # see discussion at https://github.com/geopandas/geopandas/pull/1421 + predicate = "contains" + sindex = left_df.sindex + input_geoms = right_df.geometry + else: + # all other predicates are symmetric + # keep them the same + predicate = op + sindex = right_df.sindex + input_geoms = left_df.geometry + + if sindex: + l_idx, r_idx = sindex.query_bulk(input_geoms, predicate=predicate, sort=False) + result = pd.DataFrame({"_key_left": l_idx, "_key_right": r_idx}) else: - tree_idx = left_df.sindex - tree_idx_right = False + # when sindex is empty / has no valid geometries + result = pd.DataFrame(columns=["_key_left", "_key_right"], dtype=float) + if op == "within": + # within is implemented as the inverse of contains + # flip back the results + result = result.rename( + columns={"_key_left": "_key_right", "_key_right": "_key_left"} + ) - # the rtree spatial index only allows limited (numeric) index types, but an + # the spatial index only allows limited (numeric) index types, but an # index in geopandas may be any arbitrary dtype. so reset both indices now # and store references to the original indices, to be reaffixed later. # GH 352 @@ -95,7 +111,8 @@ left_df.index = left_df.index.rename(index_left) except TypeError: index_left = [ - "index_%s" % lsuffix + str(l) for l, ix in enumerate(left_df.index.names) + "index_%s" % lsuffix + str(pos) + for pos, ix in enumerate(left_df.index.names) ] left_index_name = left_df.index.names left_df.index = left_df.index.rename(index_left) @@ -107,85 +124,14 @@ right_df.index = right_df.index.rename(index_right) except TypeError: index_right = [ - "index_%s" % rsuffix + str(l) for l, ix in enumerate(right_df.index.names) + "index_%s" % rsuffix + str(pos) + for pos, ix in enumerate(right_df.index.names) ] right_index_name = right_df.index.names right_df.index = right_df.index.rename(index_right) right_df = right_df.reset_index() - if op == "within": - # within implemented as the inverse of contains; swap names - left_df, right_df = right_df, left_df - tree_idx_right = not tree_idx_right - - r_idx = np.empty((0, 0)) - l_idx = np.empty((0, 0)) - # get rtree spatial index - if tree_idx_right: - idxmatch = left_df.geometry.apply(lambda x: x.bounds).apply( - lambda x: list(tree_idx.intersection(x)) if not x == () else [] - ) - idxmatch = idxmatch[idxmatch.apply(len) > 0] - # indexes of overlapping boundaries - if idxmatch.shape[0] > 0: - r_idx = np.concatenate(idxmatch.values) - l_idx = np.concatenate([[i] * len(v) for i, v in idxmatch.iteritems()]) - else: - # tree_idx_df == 'left' - idxmatch = right_df.geometry.apply(lambda x: x.bounds).apply( - lambda x: list(tree_idx.intersection(x)) if not x == () else [] - ) - idxmatch = idxmatch[idxmatch.apply(len) > 0] - if idxmatch.shape[0] > 0: - # indexes of overlapping boundaries - l_idx = np.concatenate(idxmatch.values) - r_idx = np.concatenate([[i] * len(v) for i, v in idxmatch.iteritems()]) - - if len(r_idx) > 0 and len(l_idx) > 0: - # Vectorize predicate operations - def find_intersects(a1, a2): - return a1.intersects(a2) - - def find_contains(a1, a2): - return a1.contains(a2) - - predicate_d = { - "intersects": find_intersects, - "contains": find_contains, - "within": find_contains, - } - - check_predicates = np.vectorize(predicate_d[op]) - - result = pd.DataFrame( - np.column_stack( - [ - l_idx, - r_idx, - check_predicates( - left_df.geometry.apply(lambda x: prepared.prep(x))[l_idx], - right_df[right_df.geometry.name][r_idx], - ), - ] - ) - ) - - result.columns = ["_key_left", "_key_right", "match_bool"] - result = pd.DataFrame(result[result["match_bool"] == 1]).drop( - "match_bool", axis=1 - ) - - else: - # when output from the join has no overlapping geometries - result = pd.DataFrame(columns=["_key_left", "_key_right"], dtype=float) - - if op == "within": - # within implemented as the inverse of contains; swap names - left_df, right_df = right_df, left_df - result = result.rename( - columns={"_key_left": "_key_right", "_key_right": "_key_left"} - ) - + # perform join on the dataframes if how == "inner": result = result.set_index("_key_left") joined = ( diff -Nru python-geopandas-0.7.0/geopandas/tools/tests/test_clip.py python-geopandas-0.8.1/geopandas/tools/tests/test_clip.py --- python-geopandas-0.7.0/geopandas/tools/tests/test_clip.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tools/tests/test_clip.py 2020-07-15 17:54:36.000000000 +0000 @@ -9,11 +9,16 @@ import geopandas from geopandas import GeoDataFrame, GeoSeries, clip -from geopandas.testing import assert_geodataframe_equal +from geopandas.testing import assert_geodataframe_equal, assert_geoseries_equal import pytest +pytestmark = pytest.mark.skipif( + not geopandas.sindex.has_sindex(), reason="clip requires spatial index" +) + + @pytest.fixture def point_gdf(): """Create a point GeoDataFrame.""" @@ -23,6 +28,24 @@ @pytest.fixture +def pointsoutside_nooverlap_gdf(): + """Create a point GeoDataFrame. Its points are all outside the single + rectangle, and its bounds are outside the single rectangle's.""" + pts = np.array([[5, 15], [15, 15], [15, 20]]) + gdf = GeoDataFrame([Point(xy) for xy in pts], columns=["geometry"], crs="EPSG:4326") + return gdf + + +@pytest.fixture +def pointsoutside_overlap_gdf(): + """Create a point GeoDataFrame. Its points are all outside the single + rectangle, and its bounds are overlapping the single rectangle's.""" + pts = np.array([[5, 15], [15, 15], [15, 5]]) + gdf = GeoDataFrame([Point(xy) for xy in pts], columns=["geometry"], crs="EPSG:4326") + return gdf + + +@pytest.fixture def single_rectangle_gdf(): """Create a single rectangle for clipping.""" poly_inters = Polygon([(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)]) @@ -159,7 +182,7 @@ def test_non_overlapping_geoms(): - """Test that a bounding box returns error if the extents don't overlap""" + """Test that a bounding box returns empty if the extents don't overlap""" unit_box = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)]) unit_gdf = GeoDataFrame([1], geometry=[unit_box], crs="EPSG:4326") non_overlapping_gdf = unit_gdf.copy() @@ -167,9 +190,9 @@ lambda x: shapely.affinity.translate(x, xoff=20) ) out = clip(unit_gdf, non_overlapping_gdf) - assert_geodataframe_equal( - out, GeoDataFrame(columns=unit_gdf.columns, crs=unit_gdf.crs) - ) + assert_geodataframe_equal(out, unit_gdf.iloc[:0]) + out2 = clip(unit_gdf.geometry, non_overlapping_gdf) + assert_geoseries_equal(out2, GeoSeries(crs=unit_gdf.crs)) def test_clip_points(point_gdf, single_rectangle_gdf): @@ -313,6 +336,18 @@ assert "LineString" == clipped.geom_type[1] +def test_clip_no_box_overlap(pointsoutside_nooverlap_gdf, single_rectangle_gdf): + """Test clip when intersection is empty and boxes do not overlap.""" + clipped = clip(pointsoutside_nooverlap_gdf, single_rectangle_gdf) + assert len(clipped) == 0 + + +def test_clip_box_overlap(pointsoutside_overlap_gdf, single_rectangle_gdf): + """Test clip when intersection is emtpy and boxes do overlap.""" + clipped = clip(pointsoutside_overlap_gdf, single_rectangle_gdf) + assert len(clipped) == 0 + + def test_warning_extra_geoms_mixed(single_rectangle_gdf, mixed_gdf): """Test the correct warnings are raised if keep_geom_type is called on a mixed GDF""" @@ -325,3 +360,8 @@ called on a GDF with GeometryCollection""" with pytest.warns(UserWarning): clip(geomcol_gdf, single_rectangle_gdf, keep_geom_type=True) + + +def test_warning_crs_mismatch(point_gdf, single_rectangle_gdf): + with pytest.warns(UserWarning, match="CRS mismatch between the CRS"): + clip(point_gdf, single_rectangle_gdf.to_crs(3857)) diff -Nru python-geopandas-0.7.0/geopandas/tools/tests/test_sjoin.py python-geopandas-0.8.1/geopandas/tools/tests/test_sjoin.py --- python-geopandas-0.7.0/geopandas/tools/tests/test_sjoin.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/tools/tests/test_sjoin.py 2020-07-15 17:54:36.000000000 +0000 @@ -1,15 +1,22 @@ +from distutils.version import LooseVersion + import numpy as np import pandas as pd from shapely.geometry import Point, Polygon, GeometryCollection import geopandas -from geopandas import GeoDataFrame, GeoSeries, base, read_file, sjoin +from geopandas import GeoDataFrame, GeoSeries, read_file, sindex, sjoin from pandas.testing import assert_frame_equal import pytest +pytestmark = pytest.mark.skipif( + not sindex.has_sindex(), reason="sjoin requires spatial index" +) + + @pytest.fixture() def dfs(request): polys1 = GeoSeries( @@ -83,13 +90,12 @@ return [request.param, df1, df2, expected] -@pytest.mark.skipif(not base.HAS_SINDEX, reason="Rtree absent, skipping") class TestSpatialJoin: @pytest.mark.parametrize("dfs", ["default-index", "string-index"], indirect=True) def test_crs_mismatch(self, dfs): index, df1, df2, expected = dfs df1.crs = "epsg:4326" - with pytest.warns(UserWarning): + with pytest.warns(UserWarning, match="CRS mismatch between the CRS"): sjoin(df1, df2) @pytest.mark.parametrize( @@ -186,7 +192,7 @@ assert_frame_equal(res, exp) def test_empty_join(self): - # Check empty joins + # Check joins resulting in empty gdfs. polygons = geopandas.GeoDataFrame( { "col2": [1, 2], @@ -204,6 +210,32 @@ empty = sjoin(not_in, polygons, how="inner", op="intersects") assert empty.empty + @pytest.mark.parametrize("op", ["intersects", "contains", "within"]) + @pytest.mark.parametrize( + "empty", + [ + GeoDataFrame(geometry=[GeometryCollection(), GeometryCollection()]), + GeoDataFrame(geometry=GeoSeries()), + ], + ) + def test_join_with_empty(self, op, empty): + # Check joins with empty geometry columns/dataframes. + polygons = geopandas.GeoDataFrame( + { + "col2": [1, 2], + "geometry": [ + Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]), + Polygon([(1, 0), (2, 0), (2, 1), (1, 1)]), + ], + } + ) + result = sjoin(empty, polygons, how="left", op=op) + assert result.index_right.isnull().all() + result = sjoin(empty, polygons, how="right", op=op) + assert result.index_left.isnull().all() + result = sjoin(empty, polygons, how="inner", op=op) + assert result.empty + @pytest.mark.parametrize("dfs", ["default-index", "string-index"], indirect=True) def test_sjoin_invalid_args(self, dfs): index, df1, df2, expected = dfs @@ -262,10 +294,13 @@ ) exp.index.names = df2.index.names + # GH 1364 fix of behaviour was done in pandas 1.1.0 + if op == "within" and str(pd.__version__) >= LooseVersion("1.1.0"): + exp = exp.sort_index() + assert_frame_equal(res, exp, check_index_type=False) -@pytest.mark.skipif(not base.HAS_SINDEX, reason="Rtree absent, skipping") class TestSpatialJoinNYBB: def setup_method(self): nybb_filename = geopandas.datasets.get_path("nybb") @@ -383,7 +418,7 @@ axis=1, ) - expected_inner = GeoDataFrame(expected_inner_df, crs="epsg:4326") + expected_inner = GeoDataFrame(expected_inner_df) expected_right_df = pd.concat( [ @@ -400,9 +435,7 @@ axis=1, ) - expected_right = GeoDataFrame(expected_right_df, crs="epsg:4326").set_index( - "index_right" - ) + expected_right = GeoDataFrame(expected_right_df).set_index("index_right") expected_left_df = pd.concat( [ @@ -413,7 +446,7 @@ axis=1, ) - expected_left = GeoDataFrame(expected_left_df, crs="epsg:4326") + expected_left = GeoDataFrame(expected_left_df) assert expected_inner.equals(df_inner) assert expected_right.equals(df_right) @@ -432,8 +465,16 @@ df2 = sjoin(self.pointdf, self.polydf.append(empty), how="left") assert df2.shape == (21, 8) + @pytest.mark.parametrize("op", ["intersects", "within", "contains"]) + def test_sjoin_no_valid_geoms(self, op): + """Tests a completely empty GeoDataFrame.""" + empty = GeoDataFrame(geometry=[], crs=self.pointdf.crs) + assert sjoin(self.pointdf, empty, how="inner", op=op).empty + assert sjoin(self.pointdf, empty, how="right", op=op).empty + assert sjoin(empty, self.pointdf, how="inner", op=op).empty + assert sjoin(empty, self.pointdf, how="left", op=op).empty + -@pytest.mark.skipif(not base.HAS_SINDEX, reason="Rtree absent, skipping") class TestSpatialJoinNaturalEarth: def setup_method(self): world_path = geopandas.datasets.get_path("naturalearth_lowres") diff -Nru python-geopandas-0.7.0/geopandas/_vectorized.py python-geopandas-0.8.1/geopandas/_vectorized.py --- python-geopandas-0.7.0/geopandas/_vectorized.py 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/_vectorized.py 2020-07-15 17:54:36.000000000 +0000 @@ -0,0 +1,891 @@ +""" +Compatibility shim for the vectorized geometry operations. + +Uses PyGEOS if available/set, otherwise loops through Shapely geometries. + +""" +import warnings + +import numpy as np + +import shapely.geometry +import shapely.geos +import shapely.wkb +import shapely.wkt + +from shapely.geometry.base import BaseGeometry + +from . import _compat as compat + +try: + import pygeos +except ImportError: + geos = None + + +_names = { + "NAG": None, + "POINT": "Point", + "LINESTRING": "LineString", + "LINEARRING": "LinearRing", + "POLYGON": "Polygon", + "MULTIPOINT": "MultiPoint", + "MULTILINESTRING": "MultiLineString", + "MULTIPOLYGON": "MultiPolygon", + "GEOMETRYCOLLECTION": "GeometryCollection", +} + +if compat.USE_PYGEOS: + type_mapping = {p.value: _names[p.name] for p in pygeos.GeometryType} + geometry_type_ids = list(type_mapping.keys()) + geometry_type_values = np.array(list(type_mapping.values()), dtype=object) +else: + type_mapping, geometry_type_ids, geometry_type_values = None, None, None + + +def _isna(value): + """ + Check if scalar value is NA-like (None or np.nan). + + Custom version that only works for scalars (returning True or False), + as `pd.isna` also works for array-like input returning a boolean array. + """ + if value is None: + return True + elif isinstance(value, float) and np.isnan(value): + return True + else: + return False + + +def _pygeos_to_shapely(geom): + if geom is None: + return None + + if compat.PYGEOS_SHAPELY_COMPAT: + geom = shapely.geos.lgeos.GEOSGeom_clone(geom._ptr) + return shapely.geometry.base.geom_factory(geom) + + # fallback going through WKB + if pygeos.is_empty(geom) and pygeos.get_type_id(geom) == 0: + # empty point does not roundtrip through WKB + return shapely.wkt.loads("POINT EMPTY") + else: + return shapely.wkb.loads(pygeos.to_wkb(geom)) + + +def _shapely_to_pygeos(geom): + if geom is None: + return None + + if compat.PYGEOS_SHAPELY_COMPAT: + return pygeos.from_shapely(geom) + + # fallback going through WKB + if geom.is_empty and geom.geom_type == "Point": + # empty point does not roundtrip through WKB + return pygeos.from_wkt("POINT EMPTY") + else: + return pygeos.from_wkb(geom.wkb) + + +def from_shapely(data): + """ + Convert a list or array of shapely objects to an object-dtype numpy + array of validated geometry elements. + + """ + # First try a fast path for pygeos if possible, but do this in a try-except + # block because pygeos.from_shapely only handles Shapely objects, while + # the rest of this function is more forgiving (also __geo_interface__). + if compat.USE_PYGEOS and compat.PYGEOS_SHAPELY_COMPAT: + if not isinstance(data, np.ndarray): + arr = np.empty(len(data), dtype=object) + arr[:] = data + else: + arr = data + try: + return pygeos.from_shapely(arr) + except TypeError: + pass + + out = [] + + for geom in data: + if compat.USE_PYGEOS and isinstance(geom, pygeos.Geometry): + out.append(geom) + elif isinstance(geom, BaseGeometry): + if compat.USE_PYGEOS: + out.append(_shapely_to_pygeos(geom)) + else: + out.append(geom) + elif hasattr(geom, "__geo_interface__"): + geom = shapely.geometry.asShape(geom) + # asShape returns GeometryProxy -> trigger actual materialization + # with one of its methods + geom.wkb + if compat.USE_PYGEOS: + out.append(_shapely_to_pygeos(geom)) + else: + out.append(geom) + elif _isna(geom): + out.append(None) + else: + raise TypeError("Input must be valid geometry objects: {0}".format(geom)) + + if compat.USE_PYGEOS: + return np.array(out, dtype=object) + else: + # numpy can expand geometry collections into 2D arrays, use this + # two-step construction to avoid this + aout = np.empty(len(data), dtype=object) + aout[:] = out + return aout + + +def to_shapely(data): + if compat.USE_PYGEOS: + out = np.empty(len(data), dtype=object) + out[:] = [_pygeos_to_shapely(geom) for geom in data] + return out + else: + return data + + +def from_wkb(data): + """ + Convert a list or array of WKB objects to a np.ndarray[geoms]. + """ + if compat.USE_PYGEOS: + return pygeos.from_wkb(data) + + import shapely.wkb + + out = [] + + for geom in data: + if geom is not None and len(geom): + geom = shapely.wkb.loads(geom) + else: + geom = None + out.append(geom) + + aout = np.empty(len(data), dtype=object) + aout[:] = out + return aout + + +def to_wkb(data, hex=False): + if compat.USE_PYGEOS: + return pygeos.to_wkb(data, hex=hex) + else: + if hex: + out = [geom.wkb_hex if geom is not None else None for geom in data] + else: + out = [geom.wkb if geom is not None else None for geom in data] + return np.array(out, dtype=object) + + +def from_wkt(data): + """ + Convert a list or array of WKT objects to a np.ndarray[geoms]. + """ + if compat.USE_PYGEOS: + return pygeos.from_wkt(data) + + import shapely.wkt + + out = [] + + for geom in data: + if geom is not None and len(geom): + if isinstance(geom, bytes): + geom = geom.decode("utf-8") + geom = shapely.wkt.loads(geom) + else: + geom = None + out.append(geom) + + aout = np.empty(len(data), dtype=object) + aout[:] = out + return aout + + +def to_wkt(data, **kwargs): + if compat.USE_PYGEOS: + return pygeos.to_wkt(data, **kwargs) + else: + out = [geom.wkt if geom is not None else None for geom in data] + return np.array(out, dtype=object) + + +def _points_from_xy(x, y, z=None): + # helper method for shapely-based function + if not len(x) == len(y): + raise ValueError("x and y arrays must be equal length.") + if z is not None: + if not len(z) == len(x): + raise ValueError("z array must be same length as x and y.") + geom = [shapely.geometry.Point(i, j, k) for i, j, k in zip(x, y, z)] + else: + geom = [shapely.geometry.Point(i, j) for i, j in zip(x, y)] + return geom + + +def points_from_xy(x, y, z=None): + + x = np.asarray(x, dtype="float64") + y = np.asarray(y, dtype="float64") + if z is not None: + z = np.asarray(z, dtype="float64") + + if compat.USE_PYGEOS: + return pygeos.points(x, y, z) + else: + out = _points_from_xy(x, y, z) + aout = np.empty(len(x), dtype=object) + aout[:] = out + return aout + + +# ----------------------------------------------------------------------------- +# Helper methods for the vectorized operations +# ----------------------------------------------------------------------------- + + +def _binary_method(op, left, right, **kwargs): + # type: (str, np.array[geoms], [np.array[geoms]/BaseGeometry]) -> array-like + if isinstance(right, BaseGeometry): + right = from_shapely([right])[0] + return getattr(pygeos, op)(left, right, **kwargs) + + +def _binary_geo(op, left, right): + # type: (str, np.array[geoms], [np.array[geoms]/BaseGeometry]) -> np.array[geoms] + """ Apply geometry-valued operation + + Supports: + + - difference + - symmetric_difference + - intersection + - union + + Parameters + ---------- + op: string + right: np.array[geoms] or single shapely BaseGeoemtry + """ + if isinstance(right, BaseGeometry): + # intersection can return empty GeometryCollections, and if the + # result are only those, numpy will coerce it to empty 2D array + data = np.empty(len(left), dtype=object) + data[:] = [ + getattr(s, op)(right) if s is not None and right is not None else None + for s in left + ] + return data + elif isinstance(right, np.ndarray): + if len(left) != len(right): + msg = "Lengths of inputs do not match. Left: {0}, Right: {1}".format( + len(left), len(right) + ) + raise ValueError(msg) + data = np.empty(len(left), dtype=object) + data[:] = [ + getattr(this_elem, op)(other_elem) + if this_elem is not None and other_elem is not None + else None + for this_elem, other_elem in zip(left, right) + ] + return data + else: + raise TypeError("Type not known: {0} vs {1}".format(type(left), type(right))) + + +def _binary_predicate(op, left, right, *args, **kwargs): + # type: (str, np.array[geoms], np.array[geoms]/BaseGeometry, args/kwargs) + # -> array[bool] + """Binary operation on np.array[geoms] that returns a boolean ndarray + + Supports: + + - contains + - disjoint + - intersects + - touches + - crosses + - within + - overlaps + - covers + - covered_by + - equals + + Parameters + ---------- + op: string + right: np.array[geoms] or single shapely BaseGeoemtry + """ + # empty geometries are handled by shapely (all give False except disjoint) + if isinstance(right, BaseGeometry): + data = [ + getattr(s, op)(right, *args, **kwargs) if s is not None else False + for s in left + ] + return np.array(data, dtype=bool) + elif isinstance(right, np.ndarray): + data = [ + getattr(this_elem, op)(other_elem, *args, **kwargs) + if not (this_elem is None or other_elem is None) + else False + for this_elem, other_elem in zip(left, right) + ] + return np.array(data, dtype=bool) + else: + raise TypeError("Type not known: {0} vs {1}".format(type(left), type(right))) + + +def _binary_op_float(op, left, right, *args, **kwargs): + # type: (str, np.array[geoms], np.array[geoms]/BaseGeometry, args/kwargs) + # -> array + """Binary operation on np.array[geoms] that returns a ndarray""" + # used for distance -> check for empty as we want to return np.nan instead 0.0 + # as shapely does currently (https://github.com/Toblerity/Shapely/issues/498) + if isinstance(right, BaseGeometry): + data = [ + getattr(s, op)(right, *args, **kwargs) + if not (s is None or s.is_empty or right.is_empty) + else np.nan + for s in left + ] + return np.array(data, dtype=float) + elif isinstance(right, np.ndarray): + if len(left) != len(right): + msg = "Lengths of inputs do not match. Left: {0}, Right: {1}".format( + len(left), len(right) + ) + raise ValueError(msg) + data = [ + getattr(this_elem, op)(other_elem, *args, **kwargs) + if not (this_elem is None or this_elem.is_empty) + | (other_elem is None or other_elem.is_empty) + else np.nan + for this_elem, other_elem in zip(left, right) + ] + return np.array(data, dtype=float) + else: + raise TypeError("Type not known: {0} vs {1}".format(type(left), type(right))) + + +def _binary_op(op, left, right, *args, **kwargs): + # type: (str, np.array[geoms], np.array[geoms]/BaseGeometry, args/kwargs) + # -> array + """Binary operation on np.array[geoms] that returns a ndarray""" + # pass empty to shapely (relate handles this correctly, project only + # for linestrings and points) + if op == "project": + null_value = np.nan + dtype = float + elif op == "relate": + null_value = None + dtype = object + else: + raise AssertionError("wrong op") + + if isinstance(right, BaseGeometry): + data = [ + getattr(s, op)(right, *args, **kwargs) if s is not None else null_value + for s in left + ] + return np.array(data, dtype=dtype) + elif isinstance(right, np.ndarray): + if len(left) != len(right): + msg = "Lengths of inputs do not match. Left: {0}, Right: {1}".format( + len(left), len(right) + ) + raise ValueError(msg) + data = [ + getattr(this_elem, op)(other_elem, *args, **kwargs) + if not (this_elem is None or other_elem is None) + else null_value + for this_elem, other_elem in zip(left, right) + ] + return np.array(data, dtype=dtype) + else: + raise TypeError("Type not known: {0} vs {1}".format(type(left), type(right))) + + +def _affinity_method(op, left, *args, **kwargs): + # type: (str, np.array[geoms], ...) -> np.array[geoms] + + # not all shapely.affinity methods can handle empty geometries: + # affine_transform itself works (as well as translate), but rotate, scale + # and skew fail (they try to unpack the bounds). + # Here: consistently returning empty geom for input empty geom + left = to_shapely(left) + out = [] + for geom in left: + if geom is None or geom.is_empty: + res = geom + else: + res = getattr(shapely.affinity, op)(geom, *args, **kwargs) + out.append(res) + data = np.empty(len(left), dtype=object) + data[:] = out + return from_shapely(data) + + +# ----------------------------------------------------------------------------- +# Vectorized operations +# ----------------------------------------------------------------------------- + + +# +# Unary operations that return non-geometry (bool or float) +# + + +def _unary_op(op, left, null_value=False): + # type: (str, np.array[geoms], Any) -> np.array + """Unary operation that returns a Series""" + data = [getattr(geom, op, null_value) for geom in left] + return np.array(data, dtype=np.dtype(type(null_value))) + + +def is_valid(data): + if compat.USE_PYGEOS: + return pygeos.is_valid(data) + else: + return _unary_op("is_valid", data, null_value=False) + + +def is_empty(data): + if compat.USE_PYGEOS: + return pygeos.is_empty(data) + else: + return _unary_op("is_empty", data, null_value=False) + + +def is_simple(data): + if compat.USE_PYGEOS: + return pygeos.is_simple(data) + else: + return _unary_op("is_simple", data, null_value=False) + + +def is_ring(data): + if compat.USE_PYGEOS: + return pygeos.is_ring(pygeos.get_exterior_ring(data)) + else: + # operates on the exterior, so can't use _unary_op() + # XXX needed to change this because there is now a geometry collection + # in the shapely ones that was something else before? + return np.array( + [ + geom.exterior.is_ring + if geom is not None + and hasattr(geom, "exterior") + and geom.exterior is not None + else False + for geom in data + ], + dtype=bool, + ) + + +def is_closed(data): + if compat.USE_PYGEOS: + return pygeos.is_closed(data) + else: + return _unary_op("is_closed", data, null_value=False) + + +def has_z(data): + if compat.USE_PYGEOS: + return pygeos.has_z(data) + else: + return _unary_op("has_z", data, null_value=False) + + +def geom_type(data): + if compat.USE_PYGEOS: + res = pygeos.get_type_id(data) + return geometry_type_values[np.searchsorted(geometry_type_ids, res)] + else: + return _unary_op("geom_type", data, null_value=None) + + +def area(data): + if compat.USE_PYGEOS: + return pygeos.area(data) + else: + return _unary_op("area", data, null_value=np.nan) + + +def length(data): + if compat.USE_PYGEOS: + return pygeos.length(data) + else: + return _unary_op("length", data, null_value=np.nan) + + +# +# Unary operations that return new geometries +# + + +def _unary_geo(op, left, *args, **kwargs): + # type: (str, np.array[geoms]) -> np.array[geoms] + """Unary operation that returns new geometries""" + # ensure 1D output, see note above + data = np.empty(len(left), dtype=object) + data[:] = [getattr(geom, op, None) for geom in left] + return data + + +def boundary(data): + if compat.USE_PYGEOS: + return pygeos.boundary(data) + else: + return _unary_geo("boundary", data) + + +def centroid(data): + if compat.USE_PYGEOS: + return pygeos.centroid(data) + else: + return _unary_geo("centroid", data) + + +def convex_hull(data): + if compat.USE_PYGEOS: + return pygeos.convex_hull(data) + else: + return _unary_geo("convex_hull", data) + + +def envelope(data): + if compat.USE_PYGEOS: + return pygeos.envelope(data) + else: + return _unary_geo("envelope", data) + + +def exterior(data): + if compat.USE_PYGEOS: + return pygeos.get_exterior_ring(data) + else: + return _unary_geo("exterior", data) + + +def interiors(data): + data = to_shapely(data) + has_non_poly = False + inner_rings = [] + for geom in data: + interior_ring_seq = getattr(geom, "interiors", None) + # polygon case + if interior_ring_seq is not None: + inner_rings.append(list(interior_ring_seq)) + # non-polygon case + else: + has_non_poly = True + inner_rings.append(None) + if has_non_poly: + warnings.warn( + "Only Polygon objects have interior rings. For other " + "geometry types, None is returned." + ) + data = np.empty(len(data), dtype=object) + data[:] = inner_rings + return data + + +def representative_point(data): + if compat.USE_PYGEOS: + return pygeos.point_on_surface(data) + else: + # method and not a property -> can't use _unary_geo + out = np.empty(len(data), dtype=object) + out[:] = [ + geom.representative_point() if geom is not None else None for geom in data + ] + return out + + +# +# Binary predicates +# + + +def covers(data, other): + if compat.USE_PYGEOS: + return _binary_method("covers", data, other) + else: + return _binary_predicate("covers", data, other) + + +def covered_by(data, other): + if compat.USE_PYGEOS: + return _binary_method("covered_by", data, other) + else: + raise NotImplementedError( + "covered_by is only implemented for pygeos, not shapely" + ) + + +def contains(data, other): + if compat.USE_PYGEOS: + return _binary_method("contains", data, other) + else: + return _binary_predicate("contains", data, other) + + +def crosses(data, other): + if compat.USE_PYGEOS: + return _binary_method("crosses", data, other) + else: + return _binary_predicate("crosses", data, other) + + +def disjoint(data, other): + if compat.USE_PYGEOS: + return _binary_method("disjoint", data, other) + else: + return _binary_predicate("disjoint", data, other) + + +def equals(data, other): + if compat.USE_PYGEOS: + return _binary_method("equals", data, other) + else: + return _binary_predicate("equals", data, other) + + +def intersects(data, other): + if compat.USE_PYGEOS: + return _binary_method("intersects", data, other) + else: + return _binary_predicate("intersects", data, other) + + +def overlaps(data, other): + if compat.USE_PYGEOS: + return _binary_method("overlaps", data, other) + else: + return _binary_predicate("overlaps", data, other) + + +def touches(data, other): + if compat.USE_PYGEOS: + return _binary_method("touches", data, other) + else: + return _binary_predicate("touches", data, other) + + +def within(data, other): + if compat.USE_PYGEOS: + return _binary_method("within", data, other) + else: + return _binary_predicate("within", data, other) + + +def equals_exact(data, other, tolerance): + if compat.USE_PYGEOS: + return _binary_method("equals_exact", data, other, tolerance=tolerance) + else: + return _binary_predicate("equals_exact", data, other, tolerance=tolerance) + + +def almost_equals(self, other, decimal): + if compat.USE_PYGEOS: + return self.equals_exact(other, 0.5 * 10 ** (-decimal)) + else: + return _binary_predicate("almost_equals", self, other, decimal=decimal) + + +# +# Binary operations that return new geometries +# + + +def difference(data, other): + if compat.USE_PYGEOS: + return _binary_method("difference", data, other) + else: + return _binary_geo("difference", data, other) + + +def intersection(data, other): + if compat.USE_PYGEOS: + return _binary_method("intersection", data, other) + else: + return _binary_geo("intersection", data, other) + + +def symmetric_difference(data, other): + if compat.USE_PYGEOS: + return _binary_method("symmetric_difference", data, other) + else: + return _binary_geo("symmetric_difference", data, other) + + +def union(data, other): + if compat.USE_PYGEOS: + return _binary_method("union", data, other) + else: + return _binary_geo("union", data, other) + + +# +# Other operations +# + + +def distance(data, other): + if compat.USE_PYGEOS: + return _binary_method("distance", data, other) + else: + return _binary_op_float("distance", data, other) + + +def buffer(data, distance, resolution=16, **kwargs): + if compat.USE_PYGEOS: + return pygeos.buffer(data, distance, quadsegs=resolution, **kwargs) + else: + out = np.empty(len(data), dtype=object) + if isinstance(distance, np.ndarray): + if len(distance) != len(data): + raise ValueError( + "Length of distance sequence does not match " + "length of the GeoSeries" + ) + + out[:] = [ + geom.buffer(dist, resolution, **kwargs) if geom is not None else None + for geom, dist in zip(data, distance) + ] + return out + + out[:] = [ + geom.buffer(distance, resolution, **kwargs) if geom is not None else None + for geom in data + ] + return out + + +def interpolate(data, distance, normalized=False): + if compat.USE_PYGEOS: + return pygeos.line_interpolate_point(data, distance, normalize=normalized) + else: + out = np.empty(len(data), dtype=object) + if isinstance(distance, np.ndarray): + if len(distance) != len(data): + raise ValueError( + "Length of distance sequence does not match " + "length of the GeoSeries" + ) + out[:] = [ + geom.interpolate(dist, normalized=normalized) + for geom, dist in zip(data, distance) + ] + return out + + out[:] = [geom.interpolate(distance, normalized=normalized) for geom in data] + return out + + +def simplify(data, tolerance, preserve_topology=True): + if compat.USE_PYGEOS: + # preserve_topology has different default as pygeos! + return pygeos.simplify(data, tolerance, preserve_topology=preserve_topology) + else: + # method and not a property -> can't use _unary_geo + out = np.empty(len(data), dtype=object) + out[:] = [ + geom.simplify(tolerance, preserve_topology=preserve_topology) + for geom in data + ] + return out + + +def project(data, other, normalized=False): + if compat.USE_PYGEOS: + return pygeos.line_locate_point(data, other, normalize=normalized) + else: + return _binary_op("project", data, other, normalized=normalized) + + +def relate(data, other): + data = to_shapely(data) + if isinstance(other, np.ndarray): + other = to_shapely(other) + return _binary_op("relate", data, other) + + +def unary_union(data): + if compat.USE_PYGEOS: + return _pygeos_to_shapely(pygeos.union_all(data)) + else: + return shapely.ops.unary_union(data) + + +# +# Coordinate related properties +# + + +def get_x(data): + if compat.USE_PYGEOS: + return pygeos.get_x(data) + else: + return _unary_op("x", data, null_value=np.nan) + + +def get_y(data): + if compat.USE_PYGEOS: + return pygeos.get_y(data) + else: + return _unary_op("y", data, null_value=np.nan) + + +def bounds(data): + if compat.USE_PYGEOS: + return pygeos.bounds(data) + # ensure that for empty arrays, the result has the correct shape + if len(data) == 0: + return np.empty((0, 4), dtype="float64") + # need to explicitly check for empty (in addition to missing) geometries, + # as those return an empty tuple, not resulting in a 2D array + bounds = np.array( + [ + geom.bounds + if not (geom is None or geom.is_empty) + else (np.nan, np.nan, np.nan, np.nan) + for geom in data + ] + ) + return bounds + + +# +# Coordinate transformation +# + + +def transform(data, func): + if compat.USE_PYGEOS: + coords = pygeos.get_coordinates(data) + new_coords = func(coords[:, 0], coords[:, 1]) + result = pygeos.set_coordinates(data.copy(), np.array(new_coords).T) + return result + else: + from shapely.ops import transform + + n = len(data) + result = np.empty(n, dtype=object) + for i in range(n): + geom = data[i] + result[i] = transform(func, geom) + + return result diff -Nru python-geopandas-0.7.0/geopandas/_version.py python-geopandas-0.8.1/geopandas/_version.py --- python-geopandas-0.7.0/geopandas/_version.py 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/geopandas/_version.py 2020-07-15 17:54:36.000000000 +0000 @@ -22,8 +22,8 @@ # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). - git_refnames = " (HEAD -> master, tag: v0.7.0)" - git_full = "032bcbebf86fd8cae2870097e850e8427c861644" + git_refnames = " (tag: v0.8.1, 0.8.x)" + git_full = "03546f483e358b6565b11333f576e1bc68df1b57" keywords = {"refnames": git_refnames, "full": git_full} return keywords diff -Nru python-geopandas-0.7.0/requirements-dev.txt python-geopandas-0.8.1/requirements-dev.txt --- python-geopandas-0.7.0/requirements-dev.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-geopandas-0.8.1/requirements-dev.txt 2020-07-15 17:54:36.000000000 +0000 @@ -0,0 +1,33 @@ +# required +fiona>=1.7 +pandas>=0.23.4 +pyproj>=2.2.0 +shapely>=1.5 + +# geodatabase access +psycopg2>=2.5.1 +SQLAlchemy>=0.8.3 + +# geocoding +geopy + +# plotting +descartes>=1.0 +matplotlib>=2.0 +mapclassify + +# testing +mock>=1.0.1 # technically not need for python >= 3.3 +pytest>=3.1.0 +pytest-cov +codecov + +# spatial access methods +rtree>=0.8 + +# styling +black +pre-commit + +# PostGIS writing +GeoAlchemy2 diff -Nru python-geopandas-0.7.0/requirements.test.txt python-geopandas-0.8.1/requirements.test.txt --- python-geopandas-0.7.0/requirements.test.txt 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/requirements.test.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,11 +0,0 @@ -psycopg2>=2.5.1 -SQLAlchemy>=0.8.3 -geopy -matplotlib>=1.2.1 -descartes>=1.0 -mock>=1.0.1 # technically not need for python >= 3.3 -pytest>=3.1.0 -pytest-cov -codecov -rtree>=0.8 -mapclassify diff -Nru python-geopandas-0.7.0/requirements.txt python-geopandas-0.8.1/requirements.txt --- python-geopandas-0.7.0/requirements.txt 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/requirements.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -Cython>=0.16 -shapely>=1.2.18 -fiona>=1.0.1 -pyproj>=2.2.0 diff -Nru python-geopandas-0.7.0/.travis.yml python-geopandas-0.8.1/.travis.yml --- python-geopandas-0.7.0/.travis.yml 2020-02-17 07:39:12.000000000 +0000 +++ python-geopandas-0.8.1/.travis.yml 2020-07-15 17:54:36.000000000 +0000 @@ -4,20 +4,28 @@ matrix: include: - # Only one test for these Python versions + # One build with minimum versions of dependencies - env: ENV_FILE="ci/travis/35-minimal.yaml" + # one build with no optional dependencies + - env: ENV_FILE="ci/travis/38-no-optional-deps.yaml" + # Python 3.6 test all supported Pandas versions - - env: ENV_FILE="ci/travis/36-pd023.yaml" - - env: ENV_FILE="ci/travis/36-pd024.yaml" + - env: ENV_FILE="ci/travis/36-pd023.yaml" PYGEOS=true + - env: ENV_FILE="ci/travis/36-pd024.yaml" PYGEOS=true + + - env: ENV_FILE="ci/travis/37-latest-defaults.yaml" STYLE=true PYGEOS=true + - env: ENV_FILE="ci/travis/37-latest-conda-forge.yaml" PYGEOS=true - - env: ENV_FILE="ci/travis/37-latest-defaults.yaml" STYLE=true - - env: ENV_FILE="ci/travis/37-latest-conda-forge.yaml" + - env: ENV_FILE="ci/travis/38-latest-conda-forge.yaml" PYGEOS=true POSTGIS=true PGUSER=postgres PGPASSWORD=postgres - - env: ENV_FILE="ci/travis/38-latest-conda-forge.yaml" + - env: ENV_FILE="ci/travis/37-dev.yaml" DEV=true PYGEOS=true - - env: ENV_FILE="ci/travis/37-dev.yaml" DEV=true + allow_failures: + - env: ENV_FILE="ci/travis/37-dev.yaml" DEV=true PYGEOS=true +before_install: + - chmod +x ci/travis/setup_postgres.sh install: # Install conda @@ -39,12 +47,21 @@ - if [ "$DEV" ]; then pip install git+https://github.com/matplotlib/matplotlib.git; fi - if [ "$DEV" ]; then pip install git+https://github.com/Toblerity/Shapely.git; fi - if [ "$STYLE" ]; then pip install black flake8; fi + - if [ "$POSTGIS" ]; then conda install postgis -c conda-forge; fi - pip install -e . + + # List environment - conda list - python -c "import geopandas; geopandas.show_versions();" + # Set-up database + - if [ "$POSTGIS" ]; then ci/travis/setup_postgres.sh; fi + script: - - py.test geopandas --cov geopandas -v --cov-report term-missing + - echo "Testing without PyGEOS" + - USE_PYGEOS=0 pytest geopandas -v -r s --cov geopandas --cov-report term-missing + - if [ "$PYGEOS" ]; then echo "Testing with PyGEOS"; fi + - if [ "$PYGEOS" ]; then USE_PYGEOS=1 pytest geopandas -v -r s --cov-append --cov geopandas --cov-report term-missing; fi - if [ "$STYLE" ]; then black --check geopandas; fi - if [ "$STYLE" ]; then flake8 geopandas; fi