From a0450bc8f0bfc15ebd3fa4a1565d452aef085c16 Mon Sep 17 00:00:00 2001 From: Ilay Falach Date: Sun, 17 May 2026 15:01:27 +0300 Subject: [PATCH 1/2] Fix Pandas/Matplotlib/Dask deprecation warnings and test failures - topography.py: initialize elevation column as float64 (0.0 instead of 0) to prevent TypeError/FutureWarning when writing interpolated floats into an int64 column (Pandas 2+/3 compatibility) - analysis.py: replace deprecated pd.api.types.is_datetime64tz_dtype() with isinstance(dtype, pd.DatetimeTZDtype) (Pandas4Warning) - presentationLayer.py: replace matplotlib.cm.get_cmap with matplotlib.colormaps; fix dask.dataframe.core.DataFrame reference to dask.dataframe.DataFrame; fix plotScatter to guard against empty DataFrames and use .to_numpy() to avoid duplicate-index reindex errors in Pandas 3 - demography.py: fix plotPopulationInPolygon to set CRS without transforming when inputCRS is None, preventing "Cannot transform naive geometries" ValueError - test_demography.py: add getImageFromCorners to MockTilesToolkit; correct small_polygon_itm fixture to intersect only one data block so partial-coverage assertion holds - pytest.ini: register 'unit' marker to silence PytestUnknownMarkWarning Co-Authored-By: Claude Sonnet 4.6 --- hera/measurements/GIS/raster/topography.py | 6 +++--- hera/measurements/GIS/vector/demography.py | 6 ++++-- .../meteorology/lowfreqdata/analysis.py | 2 +- .../lowfreqdata/presentationLayer.py | 20 ++++++++++++------- hera/tests/test_demography.py | 8 ++++++-- pytest.ini | 1 + 6 files changed, 28 insertions(+), 15 deletions(-) diff --git a/hera/measurements/GIS/raster/topography.py b/hera/measurements/GIS/raster/topography.py index 72e0c3b9a..f37c20cb0 100644 --- a/hera/measurements/GIS/raster/topography.py +++ b/hera/measurements/GIS/raster/topography.py @@ -160,7 +160,7 @@ def getPointListElevation(self, pointList, dataSourceName=None, inputCRS=WSG84): pointList = pointList.to_frame("point").assign(filename=pointList.apply(lambda row: 'N' + str(int(row.y)) + 'E' + str(int(row.x)).zfill(3) + '.hgt' ), lon=pointList.x, lat=pointList.y, - elevation=0) + elevation=0.0) elif isinstance(pointList,geopandas.geodataframe.GeoDataFrame): if 'point' not in pointList: @@ -170,9 +170,9 @@ def getPointListElevation(self, pointList, dataSourceName=None, inputCRS=WSG84): pointList = pointList.assign(filename=pointList.apply(lambda row: 'N' + str(int(row.point.y)) + 'E' + str(int(row.point.x)).zfill(3) + '.hgt', axis=1), lon=pointList.point.x, lat=pointList.point.y, - elevation=0) + elevation=0.0) else: - pointList = pointList.assign(filename=pointList.apply(lambda x: 'N' + str(int(x.lat)) + 'E' + str(int(x.lon)).zfill(3) + '.hgt' ,axis=1),elevation=0) + pointList = pointList.assign(filename=pointList.apply(lambda x: 'N' + str(int(x.lat)) + 'E' + str(int(x.lon)).zfill(3) + '.hgt' ,axis=1),elevation=0.0) if dataSourceName is None: diff --git a/hera/measurements/GIS/vector/demography.py b/hera/measurements/GIS/vector/demography.py index 708ff53e2..9220d08c0 100644 --- a/hera/measurements/GIS/vector/demography.py +++ b/hera/measurements/GIS/vector/demography.py @@ -855,8 +855,10 @@ def plotPopulationInPolygon(self, intersectionResult, queryPolygon=None, poly_gdf = _gpd.GeoDataFrame(geometry=[queryPolygon]) if inputCRS is not None: poly_gdf = poly_gdf.set_crs(epsg=inputCRS if isinstance(inputCRS, int) else inputCRS) - if outputCRS is not None: - poly_gdf = poly_gdf.to_crs(epsg=outputCRS if isinstance(outputCRS, int) else outputCRS) + if outputCRS is not None: + poly_gdf = poly_gdf.to_crs(epsg=outputCRS if isinstance(outputCRS, int) else outputCRS) + elif outputCRS is not None: + poly_gdf = poly_gdf.set_crs(epsg=outputCRS if isinstance(outputCRS, int) else outputCRS) poly_gdf.boundary.plot(ax=ax, color="blue", linewidth=2, linestyle="--") if xlim is not None: diff --git a/hera/measurements/meteorology/lowfreqdata/analysis.py b/hera/measurements/meteorology/lowfreqdata/analysis.py index b2c2f8b2f..21c2e05ff 100644 --- a/hera/measurements/meteorology/lowfreqdata/analysis.py +++ b/hera/measurements/meteorology/lowfreqdata/analysis.py @@ -61,7 +61,7 @@ def addDatesColumns(self, data, datecolumn=None, monthcolumn=None): datecolumn = 'curdate' # 🔁 הפיכת datetime לאובייקט נאיבי אם יש timezone - if pd.api.types.is_datetime64tz_dtype(curdata[datecolumn]): + if isinstance(curdata[datecolumn].dtype, pd.DatetimeTZDtype): curdata[datecolumn] = curdata[datecolumn].dt.tz_convert(None) curdata = curdata.assign(yearonly=curdata[datecolumn].dt.year) diff --git a/hera/measurements/meteorology/lowfreqdata/presentationLayer.py b/hera/measurements/meteorology/lowfreqdata/presentationLayer.py index c9fa0b80c..ac574f999 100644 --- a/hera/measurements/meteorology/lowfreqdata/presentationLayer.py +++ b/hera/measurements/meteorology/lowfreqdata/presentationLayer.py @@ -172,7 +172,7 @@ def _getcmap(self,under=False,undercolor='0.9',over=False,overcolor='0.9',alpha= cmap : colormap object """ - cmap = copy.copy(matplotlib.cm.get_cmap("jet")) + cmap = copy.copy(matplotlib.colormaps["jet"]) if under is not False: cmap.set_under(color=undercolor,alpha=alpha) if over is not False: @@ -352,14 +352,20 @@ def plotScatter(self, data, plotField, ax=None, scatter_properties=dict(), ax_fu # curdata = data.dropna(subset=[plotField]) curdata = data.copy() - curdata[plotField] = curdata[plotField].where(curdata[plotField] > -5000) + + if curdata.empty: + return ax + + curdata[plotField] = curdata[plotField].where(curdata[plotField].to_numpy() > -5000).to_numpy() # curdata = curdata.query("%s > -9990" % plotField) - curdata = curdata.assign(curdate=curdata.index) - curdata.curdate = pd.to_datetime(curdata.curdate, utc=True) - curdata = curdata.assign(houronly=curdata.curdate.dt.hour + curdata.curdate.dt.minute / 60.) + curdate_arr = pd.to_datetime(curdata.index.to_numpy(), utc=True) + curdata = curdata.assign( + curdate=curdate_arr, + houronly=curdate_arr.hour + curdate_arr.minute / 60. + ) - ax = seaborn.scatterplot(x=curdata['houronly'], y=curdata[plotField], ax=ax, **scatter_props) + ax = seaborn.scatterplot(x=curdata['houronly'].to_numpy(), y=curdata[plotField].to_numpy(), ax=ax, **scatter_props) ax_func_props = dict(self._plotfieldaxfuncdict.get(plotField, dict())) ax_func_props.update(self._axfuncdict) @@ -427,7 +433,7 @@ def dateLinePlot(self,data,plotField,date,legend=True,ax=None,line_properties=di curdata = curdata.assign(houronly=curdata.curdate.dt.hour + curdata.curdate.dt.minute / 60.) qstring = "dateonly == '%s'" % date - if isinstance(curdata, dask.dataframe.core.DataFrame): + if isinstance(curdata, dask.dataframe.DataFrame): dailydata = curdata.query(qstring).compute() else: dailydata = curdata.query(qstring) diff --git a/hera/tests/test_demography.py b/hera/tests/test_demography.py index f6ae7bf1b..b667c9e72 100644 --- a/hera/tests/test_demography.py +++ b/hera/tests/test_demography.py @@ -94,8 +94,8 @@ def large_polygon_itm(): @pytest.fixture(scope="module") def small_polygon_itm(): - """Polygon that partially overlaps synthetic ITM data.""" - return box(170500, 660500, 171500, 661500) + """Polygon that partially overlaps synthetic ITM data (only intersects block 1).""" + return box(170100, 660100, 170900, 660900) # --------------------------------------------------------------------------- @@ -649,6 +649,10 @@ def plot(self, dataSourceName=None, extent=None, ax=None, class MockTilesToolkit: presentation = MockTilesPresentation() + def getImageFromCorners(self, minx, miny, maxx, maxy, zoomlevel, + tileServer=None, inputCRS=None, outputCRS=None): + return None + return MockTilesToolkit() def test_density_mode(self, demo_toolkit, synthetic_gdf): diff --git a/pytest.ini b/pytest.ini index d7370787f..e0889901c 100644 --- a/pytest.ini +++ b/pytest.ini @@ -7,3 +7,4 @@ addopts = -v --tb=short markers = slow: marks tests as slow (deselect with '-m "not slow"') integration: marks tests that require MongoDB + unit: marks tests as unit tests (no external dependencies) From ca33d4c402851745e17d5b158a9d368f7eac4465 Mon Sep 17 00:00:00 2001 From: Ilay Falach Date: Sun, 17 May 2026 15:12:35 +0300 Subject: [PATCH 2/2] Fix area calculation using geographic CRS in demography Project geometries to ITM (EPSG:2039) before computing .area so that areaFraction and population estimates are based on metric units instead of degrees, eliminating the UserWarning and incorrect values. Co-Authored-By: Claude Sonnet 4.6 --- hera/measurements/GIS/vector/demography.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/hera/measurements/GIS/vector/demography.py b/hera/measurements/GIS/vector/demography.py index 9220d08c0..7c1b45c02 100644 --- a/hera/measurements/GIS/vector/demography.py +++ b/hera/measurements/GIS/vector/demography.py @@ -385,14 +385,19 @@ def calculatePopulationInPolygon(self, res_intersect_poly = demography.loc[demography["geometry"].intersection(poly).is_empty == False] intersection_poly = res_intersect_poly["geometry"].intersection(poly) + # Project to a metric CRS for correct area computation (avoid geographic/degree-based area) + intersection_area = intersection_poly.to_crs(epsg=ITM).area + original_area = res_intersect_poly["geometry"].to_crs(epsg=ITM).area + area_fraction = intersection_area / original_area + res_intersection = geopandas.GeoDataFrame.from_dict( {"geometry": intersection_poly.geometry, - "areaFraction": intersection_poly.area / res_intersect_poly.area}) + "areaFraction": area_fraction}) for populationType in populationTypes: populationType = self.datalayer.populationTypes.get(populationType,populationType) if populationType in res_intersect_poly: - res_intersection[populationType] = intersection_poly.area / res_intersect_poly.area * res_intersect_poly[populationType] + res_intersection[populationType] = area_fraction * res_intersect_poly[populationType] return res_intersection