diff --git a/hera/measurements/GIS/vector/demography.py b/hera/measurements/GIS/vector/demography.py index 024a7831..7c1b45c0 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 @@ -857,6 +862,8 @@ def plotPopulationInPolygon(self, intersectionResult, queryPolygon=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) + 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 b2c2f8b2..21c2e05f 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 b9828855..46c7061c 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: @@ -361,14 +361,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) diff --git a/hera/tests/test_demography.py b/hera/tests/test_demography.py index bdec205b..ddb025dd 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 strictly inside block 1 (170000-171000, 660000-661000), pop=1000.""" - return box(170200, 660200, 170800, 660800) + """Polygon that partially overlaps synthetic ITM data (only intersects block 1).""" + return box(170100, 660100, 170900, 660900) # --------------------------------------------------------------------------- @@ -649,6 +649,9 @@ 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 def getImageFromCorners(self, minx, miny, maxx, maxy, zoomlevel=14, tileServer=None, inputCRS=None, outputCRS=None): diff --git a/pytest.ini b/pytest.ini index bdb87494..ab13bf36 100644 --- a/pytest.ini +++ b/pytest.ini @@ -8,4 +8,5 @@ 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) notebook: marks notebook execution tests (run with 'make test-notebooks', excluded from 'make test')