Skip to content

Commit

Permalink
UTM shift fix
Browse files Browse the repository at this point in the history
* Textures images.

* UTM shift fix.

* Removed texture file.

* Minor code updates.

* mypy updates.

* Default satellite margin value.
  • Loading branch information
iwatkot authored Jan 9, 2025
1 parent e40d50f commit 7716670
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 63 deletions.
21 changes: 6 additions & 15 deletions maps4fs/generator/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,7 @@ def get_bbox(
self,
coordinates: tuple[float, float] | None = None,
distance: int | None = None,
project_utm: bool = False,
) -> tuple[int, int, int, int]:
) -> tuple[float, float, float, float]:
"""Calculates the bounding box of the map from the coordinates and the height and
width of the map.
If coordinates and distance are not provided, the instance variables are used.
Expand All @@ -199,24 +198,23 @@ def get_bbox(
of the map. Defaults to None.
distance (int, optional): The distance from the center of the map to the edge of the
map in all directions. Defaults to None.
project_utm (bool, optional): Whether to project the bounding box to UTM.
Returns:
tuple[int, int, int, int]: The bounding box of the map.
tuple[float, float, float, float]: The bounding box of the map.
"""
coordinates = coordinates or self.coordinates
distance = distance or int(self.map_rotated_size / 2)

west, south, east, north = ox.utils_geo.bbox_from_point( # type: ignore
coordinates, dist=distance, project_utm=project_utm
coordinates,
dist=distance,
)

bbox = north, south, east, west
self.logger.debug(
"Calculated bounding box for component: %s: %s, project_utm: %s, distance: %s",
"Calculated bounding box for component: %s: %s, distance: %s",
self.__class__.__name__,
bbox,
project_utm,
distance,
)
return bbox
Expand All @@ -225,7 +223,7 @@ def save_bbox(self) -> None:
"""Saves the bounding box of the map to the component instance from the coordinates and the
height and width of the map.
"""
self.bbox = self.get_bbox(project_utm=False)
self.bbox = self.get_bbox()
self.logger.debug("Saved bounding box: %s", self.bbox)

@property
Expand Down Expand Up @@ -544,17 +542,10 @@ def get_z_scaling_factor(self) -> float:
"""

scaling_factor = 1 / self.map.dem_settings.multiplier
self.logger.debug("Z scaling factor including DEM multiplier: %s", scaling_factor)

if self.map.shared_settings.height_scale_multiplier:
scaling_factor *= self.map.shared_settings.height_scale_multiplier
self.logger.debug(
"Z scaling factor including height scale multiplier: %s", scaling_factor
)
if self.map.shared_settings.mesh_z_scaling_factor:
scaling_factor *= 1 / self.map.shared_settings.mesh_z_scaling_factor
self.logger.debug(
"Z scaling factor including mesh z scaling factor: %s", scaling_factor
)

return scaling_factor
2 changes: 1 addition & 1 deletion maps4fs/generator/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,5 +183,5 @@ class SatelliteSettings(SettingsModel):
"""

download_images: bool = False
satellite_margin: int = 100
satellite_margin: int = 0
zoom_level: int = 14
89 changes: 42 additions & 47 deletions maps4fs/generator/texture.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,19 +336,12 @@ def rotate_textures(self) -> None:
# pylint: disable=W0201
def _read_parameters(self) -> None:
"""Reads map parameters from OSM data, such as:
- minimum and maximum coordinates in UTM format
- minimum and maximum coordinates
- map dimensions in meters
- map coefficients (meters per pixel)
"""
north, south, east, west = self.get_bbox(project_utm=True)

# Parameters of the map in UTM format (meters).
self.minimum_x = min(west, east)
self.minimum_y = min(south, north)
self.maximum_x = max(west, east)
self.maximum_y = max(south, north)
self.logger.debug("Map minimum coordinates (XxY): %s x %s.", self.minimum_x, self.minimum_y)
self.logger.debug("Map maximum coordinates (XxY): %s x %s.", self.maximum_x, self.maximum_y)
bbox = ox.utils_geo.bbox_from_point(self.coordinates, dist=self.map_rotated_size / 2)
self.minimum_x, self.minimum_y, self.maximum_x, self.maximum_y = bbox

def info_sequence(self) -> dict[str, Any]:
"""Returns the JSON representation of the generation info for textures."""
Expand Down Expand Up @@ -576,29 +569,19 @@ def draw_base_layer(self, cumulative_image: np.ndarray) -> None:
cv2.imwrite(layer_path, img)
self.logger.debug("Base texture %s saved.", layer_path)

def get_relative_x(self, x: float) -> int:
"""Converts UTM X coordinate to relative X coordinate in map image.
def latlon_to_pixel(self, lat: float, lon: float) -> tuple[int, int]:
"""Converts latitude and longitude to pixel coordinates.
Arguments:
x (float): UTM X coordinate.
lat (float): Latitude.
lon (float): Longitude.
Returns:
int: Relative X coordinate in map image.
tuple[int, int]: Pixel coordinates.
"""
return int(self.map_rotated_size * (x - self.minimum_x) / (self.maximum_x - self.minimum_x))

def get_relative_y(self, y: float) -> int:
"""Converts UTM Y coordinate to relative Y coordinate in map image.
Arguments:
y (float): UTM Y coordinate.
Returns:
int: Relative Y coordinate in map image.
"""
return int(
self.map_rotated_size * (1 - (y - self.minimum_y) / (self.maximum_y - self.minimum_y))
)
x = int((lon - self.minimum_x) / (self.maximum_x - self.minimum_x) * self.map_rotated_size)
y = int((lat - self.maximum_y) / (self.minimum_y - self.maximum_y) * self.map_rotated_size)
return x, y

def np_to_polygon_points(self, np_array: np.ndarray) -> list[tuple[int, int]]:
"""Converts numpy array of polygon points to list of tuples.
Expand All @@ -623,11 +606,13 @@ def _to_np(self, geometry: shapely.geometry.polygon.Polygon, *args) -> np.ndarra
Returns:
np.ndarray: Numpy array of polygon points.
"""
xs, ys = geometry.exterior.coords.xy
xs = [int(self.get_relative_x(x)) for x in xs.tolist()]
ys = [int(self.get_relative_y(y)) for y in ys.tolist()]
pairs = list(zip(xs, ys))
return np.array(pairs, dtype=np.int32).reshape((-1, 1, 2))
coords = list(geometry.exterior.coords)
pts = np.array(
[self.latlon_to_pixel(coord[1], coord[0]) for coord in coords],
np.int32,
)
pts = pts.reshape((-1, 1, 2))
return pts

def _to_polygon(
self, obj: pd.core.series.Series, width: int | None
Expand Down Expand Up @@ -664,9 +649,20 @@ def _sequence(
Returns:
shapely.geometry.polygon.Polygon: Polygon geometry.
"""
polygon = geometry.buffer(width)
polygon = geometry.buffer(self.meters_to_degrees(width) if width else 0)
return polygon

def meters_to_degrees(self, meters: int) -> float:
"""Converts meters to degrees.
Arguments:
meters (int): Meters.
Returns:
float: Degrees.
"""
return meters / 111320

def _skip(
self, geometry: shapely.geometry.polygon.Polygon, *args, **kwargs
) -> shapely.geometry.polygon.Polygon:
Expand Down Expand Up @@ -724,46 +720,43 @@ def objects_generator(
except Exception as e: # pylint: disable=W0718
self.logger.debug("Error fetching objects for tags: %s. Error: %s.", tags, e)
return
objects_utm = ox.projection.project_gdf(objects, to_latlong=False)
self.logger.debug("Fetched %s elements for tags: %s.", len(objects_utm), tags)
self.logger.debug("Fetched %s elements for tags: %s.", len(objects), tags)

method = self.linestrings_generator if yield_linestrings else self.polygons_generator

yield from method(objects_utm, width, is_fieds)
yield from method(objects, width, is_fieds)

def linestrings_generator(
self, objects_utm: pd.core.frame.DataFrame, *args, **kwargs
self, objects: pd.core.frame.DataFrame, *args, **kwargs
) -> Generator[list[tuple[int, int]], None, None]:
"""Generator which yields lists of point coordinates which represent LineStrings from OSM.
Arguments:
objects_utm (pd.core.frame.DataFrame): Dataframe with OSM objects in UTM format.
objects (pd.core.frame.DataFrame): Dataframe with OSM objects.
Yields:
Generator[list[tuple[int, int]], None, None]: List of point coordinates.
"""
for _, obj in objects_utm.iterrows():
for _, obj in objects.iterrows():
geometry = obj["geometry"]
if isinstance(geometry, shapely.geometry.linestring.LineString):
points = [
(self.get_relative_x(x), self.get_relative_y(y)) for x, y in geometry.coords
]
points = [self.latlon_to_pixel(x, y) for y, x in geometry.coords]
yield points

def polygons_generator(
self, objects_utm: pd.core.frame.DataFrame, width: int | None, is_fieds: bool
self, objects: pd.core.frame.DataFrame, width: int | None, is_fieds: bool
) -> Generator[np.ndarray, None, None]:
"""Generator which yields numpy arrays of polygons from OSM data.
Arguments:
objects_utm (pd.core.frame.DataFrame): Dataframe with OSM objects in UTM format.
objects (pd.core.frame.DataFrame): Dataframe with OSM objects.
width (int | None): Width of the polygon in meters (only for LineString).
is_fieds (bool): Flag to determine if the fields should be padded.
Yields:
Generator[np.ndarray, None, None]: Numpy array of polygon points.
"""
for _, obj in objects_utm.iterrows():
for _, obj in objects.iterrows():
try:
polygon = self._to_polygon(obj, width)
except Exception as e: # pylint: disable=W0703
Expand All @@ -773,7 +766,9 @@ def polygons_generator(
continue

if is_fieds and self.map.texture_settings.fields_padding > 0:
padded_polygon = polygon.buffer(-self.map.texture_settings.fields_padding)
padded_polygon = polygon.buffer(
-self.meters_to_degrees(self.map.texture_settings.fields_padding)
)

if not isinstance(padded_polygon, shapely.geometry.polygon.Polygon):
self.logger.warning("The padding value is too high, field will not padded.")
Expand Down

0 comments on commit 7716670

Please sign in to comment.