valetudo-map-parser 0.1.9b55__py3-none-any.whl → 0.1.9b56__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,7 +29,6 @@ from .config.types import (
29
29
  )
30
30
  from .config.utils import (
31
31
  BaseHandler,
32
- # async_extract_room_outline,
33
32
  initialize_drawing_config,
34
33
  manage_drawable_elements,
35
34
  prepare_resize_params,
@@ -71,37 +70,11 @@ class ReImageHandler(BaseHandler, AutoCrop):
71
70
  self.imd = ImageDraw(self) # Image Draw
72
71
  self.rooms_handler = RandRoomsHandler(self.file_name, self.drawing_config) # Room data handler
73
72
 
74
- async def extract_room_outline_from_map(self, room_id_int, pixels):
75
- """Extract the outline of a room using the pixel data and element map.
76
-
77
- Args:
78
- room_id_int: The room ID as an integer
79
- pixels: List of pixel coordinates in the format [[x, y, z], ...]
80
-
81
- Returns:
82
- List of points forming the outline of the room
83
- """
84
- # Calculate x and y min/max from compressed pixels for rectangular fallback
85
- x_values = []
86
- y_values = []
87
- for x, y, _ in pixels:
88
- x_values.append(x)
89
- y_values.append(y)
90
-
91
- if not x_values or not y_values:
92
- return []
93
-
94
- min_x, max_x = min(x_values), max(x_values)
95
- min_y, max_y = min(y_values), max(y_values)
96
-
97
- # Always return a rectangular outline since element_map is removed
98
- return [(min_x, min_y), (max_x, min_y), (max_x, max_y), (min_x, max_y)]
99
-
100
73
  async def extract_room_properties(
101
74
  self, json_data: JsonType, destinations: JsonType
102
75
  ) -> RoomsProperties:
103
76
  """Extract the room properties."""
104
- unsorted_id = RandImageData.get_rrm_segments_ids(json_data)
77
+ # unsorted_id = RandImageData.get_rrm_segments_ids(json_data)
105
78
  size_x, size_y = RandImageData.get_rrm_image_size(json_data)
106
79
  top, left = RandImageData.get_rrm_image_position(json_data)
107
80
  try:
@@ -124,11 +97,10 @@ class ReImageHandler(BaseHandler, AutoCrop):
124
97
 
125
98
  # Update self.rooms_pos from room_properties for compatibility with other methods
126
99
  self.rooms_pos = []
127
- for room_id, props in room_properties.items():
128
- self.rooms_pos.append({
129
- "name": props["name"],
130
- "corners": props["outline"], # Use the enhanced outline
131
- })
100
+ for room_id, room_data in room_properties.items():
101
+ self.rooms_pos.append(
102
+ {"name": room_data["name"], "outline": room_data["outline"]}
103
+ )
132
104
 
133
105
  # get the zones and points data
134
106
  zone_properties = await self.async_zone_propriety(zones_data)
@@ -353,79 +325,189 @@ class ReImageHandler(BaseHandler, AutoCrop):
353
325
  _LOGGER.debug("Got Rooms Attributes.")
354
326
  return self.room_propriety
355
327
 
328
+ @staticmethod
329
+ def point_in_polygon(x: int, y: int, polygon: list) -> bool:
330
+ """
331
+ Check if a point is inside a polygon using ray casting algorithm.
332
+ Enhanced version with better handling of edge cases.
333
+
334
+ Args:
335
+ x: X coordinate of the point
336
+ y: Y coordinate of the point
337
+ polygon: List of (x, y) tuples forming the polygon
338
+
339
+ Returns:
340
+ True if the point is inside the polygon, False otherwise
341
+ """
342
+ # Ensure we have a valid polygon with at least 3 points
343
+ if len(polygon) < 3:
344
+ return False
345
+
346
+ # Make sure the polygon is closed (last point equals first point)
347
+ if polygon[0] != polygon[-1]:
348
+ polygon = polygon + [polygon[0]]
349
+
350
+ # Use winding number algorithm for better accuracy
351
+ wn = 0 # Winding number counter
352
+
353
+ # Loop through all edges of the polygon
354
+ for i in range(len(polygon) - 1): # Last vertex is first vertex
355
+ p1x, p1y = polygon[i]
356
+ p2x, p2y = polygon[i + 1]
357
+
358
+ # Test if a point is left/right/on the edge defined by two vertices
359
+ if p1y <= y: # Start y <= P.y
360
+ if p2y > y: # End y > P.y (upward crossing)
361
+ # Point left of edge
362
+ if ((p2x - p1x) * (y - p1y) - (x - p1x) * (p2y - p1y)) > 0:
363
+ wn += 1 # Valid up intersect
364
+ else: # Start y > P.y
365
+ if p2y <= y: # End y <= P.y (downward crossing)
366
+ # Point right of edge
367
+ if ((p2x - p1x) * (y - p1y) - (x - p1x) * (p2y - p1y)) < 0:
368
+ wn -= 1 # Valid down intersect
369
+
370
+ # If winding number is not 0, the point is inside the polygon
371
+ return wn != 0
372
+
356
373
  async def async_get_robot_in_room(
357
374
  self, robot_x: int, robot_y: int, angle: float
358
375
  ) -> RobotPosition:
359
376
  """Get the robot position and return in what room is."""
377
+ # First check if we already have a cached room and if the robot is still in it
378
+ if self.robot_in_room:
379
+ # If we have outline data, use point_in_polygon for accurate detection
380
+ if "outline" in self.robot_in_room:
381
+ outline = self.robot_in_room["outline"]
382
+ if self.point_in_polygon(int(robot_x), int(robot_y), outline):
383
+ temp = {
384
+ "x": robot_x,
385
+ "y": robot_y,
386
+ "angle": angle,
387
+ "in_room": self.robot_in_room["room"],
388
+ }
389
+ # Handle active zones
390
+ self.active_zones = self.shared.rand256_active_zone
391
+ self.zooming = False
392
+ if self.active_zones and (
393
+ self.robot_in_room["id"]
394
+ in range(len(self.active_zones))
395
+ ):
396
+ self.zooming = bool(
397
+ self.active_zones[self.robot_in_room["id"]]
398
+ )
399
+ else:
400
+ self.zooming = False
401
+ return temp
402
+ # Fallback to bounding box check if no outline data
403
+ elif all(
404
+ k in self.robot_in_room for k in ["left", "right", "up", "down"]
405
+ ):
406
+ if (
407
+ (self.robot_in_room["right"] <= int(robot_x) <= self.robot_in_room["left"])
408
+ and (self.robot_in_room["up"] <= int(robot_y) <= self.robot_in_room["down"])
409
+ ):
410
+ temp = {
411
+ "x": robot_x,
412
+ "y": robot_y,
413
+ "angle": angle,
414
+ "in_room": self.robot_in_room["room"],
415
+ }
416
+ # Handle active zones
417
+ self.active_zones = self.shared.rand256_active_zone
418
+ self.zooming = False
419
+ if self.active_zones and (
420
+ self.robot_in_room["id"]
421
+ in range(len(self.active_zones))
422
+ ):
423
+ self.zooming = bool(
424
+ self.active_zones[self.robot_in_room["id"]]
425
+ )
426
+ else:
427
+ self.zooming = False
428
+ return temp
360
429
 
361
- def _check_robot_position(x: int, y: int) -> bool:
362
- # Check if the robot coordinates are inside the room's corners
363
- return (
364
- self.robot_in_room["left"] >= x >= self.robot_in_room["right"]
365
- and self.robot_in_room["up"] >= y >= self.robot_in_room["down"]
366
- )
430
+ # If we don't have a cached room or the robot is not in it, search all rooms
431
+ last_room = None
432
+ room_count = 0
433
+ if self.robot_in_room:
434
+ last_room = self.robot_in_room
367
435
 
368
- # If the robot coordinates are inside the room's
369
- if self.robot_in_room and _check_robot_position(robot_x, robot_y):
436
+ # Check if the robot is far outside the normal map boundaries
437
+ # This helps prevent false positives for points very far from any room
438
+ map_boundary = 50000 # Typical map size is around 25000-30000 units for Rand25
439
+ if abs(robot_x) > map_boundary or abs(robot_y) > map_boundary:
440
+ _LOGGER.debug(
441
+ "%s robot position (%s, %s) is far outside map boundaries.",
442
+ self.file_name,
443
+ robot_x,
444
+ robot_y,
445
+ )
446
+ self.robot_in_room = last_room
447
+ self.zooming = False
370
448
  temp = {
371
449
  "x": robot_x,
372
450
  "y": robot_y,
373
451
  "angle": angle,
374
- "in_room": self.robot_in_room["room"],
452
+ "in_room": last_room["room"] if last_room else "unknown",
375
453
  }
376
- self.active_zones = self.shared.rand256_active_zone
377
- self.zooming = False
378
- if self.active_zones and (
379
- (self.robot_in_room["id"]) in range(len(self.active_zones))
380
- ): # issue #100 Index out of range
381
- self.zooming = bool(self.active_zones[self.robot_in_room["id"]])
382
454
  return temp
383
- # else we need to search and use the async method
384
- _LOGGER.debug("%s Changed room.. searching..", self.file_name)
385
- room_count = -1
386
- last_room = None
387
455
 
388
- # If no rooms data is available, return a default position
456
+ # Search through all rooms to find which one contains the robot
389
457
  if not self.rooms_pos:
390
- _LOGGER.debug("%s: No rooms data available", self.file_name)
391
- return {"x": robot_x, "y": robot_y, "angle": angle, "in_room": "unknown"}
458
+ _LOGGER.debug(
459
+ "%s: No rooms data available for robot position detection.",
460
+ self.file_name,
461
+ )
462
+ self.robot_in_room = last_room
463
+ self.zooming = False
464
+ temp = {
465
+ "x": robot_x,
466
+ "y": robot_y,
467
+ "angle": angle,
468
+ "in_room": last_room["room"] if last_room else "unknown",
469
+ }
470
+ return temp
392
471
 
393
- # If rooms data is available, search for the room
394
- if self.robot_in_room:
395
- last_room = self.robot_in_room
472
+ _LOGGER.debug("%s: Searching for robot in rooms...", self.file_name)
396
473
  for room in self.rooms_pos:
397
- corners = room["corners"]
474
+ # Check if the room has an outline (polygon points)
475
+ if "outline" in room:
476
+ outline = room["outline"]
477
+ # Use point_in_polygon for accurate detection with complex shapes
478
+ if self.point_in_polygon(int(robot_x), int(robot_y), outline):
479
+ # Robot is in this room
480
+ self.robot_in_room = {
481
+ "id": room_count,
482
+ "room": str(room["name"]),
483
+ "outline": outline,
484
+ }
485
+ temp = {
486
+ "x": robot_x,
487
+ "y": robot_y,
488
+ "angle": angle,
489
+ "in_room": self.robot_in_room["room"],
490
+ }
491
+ _LOGGER.debug(
492
+ "%s is in %s room (polygon detection).",
493
+ self.file_name,
494
+ self.robot_in_room["room"],
495
+ )
496
+ return temp
398
497
  room_count += 1
399
- self.robot_in_room = {
400
- "id": room_count,
401
- "left": corners[0][0],
402
- "right": corners[2][0],
403
- "up": corners[0][1],
404
- "down": corners[2][1],
405
- "room": room["name"],
406
- }
407
- # Check if the robot coordinates are inside the room's corners
408
- if _check_robot_position(robot_x, robot_y):
409
- temp = {
410
- "x": robot_x,
411
- "y": robot_y,
412
- "angle": angle,
413
- "in_room": self.robot_in_room["room"],
414
- }
415
- _LOGGER.debug("%s is in %s", self.file_name, self.robot_in_room["room"])
416
- del room, corners, robot_x, robot_y # free memory.
417
- return temp
418
- # After checking all rooms and not finding a match
498
+
499
+ # Robot not found in any room
419
500
  _LOGGER.debug(
420
- "%s: Not located within Camera Rooms coordinates.", self.file_name
501
+ "%s not located within any room coordinates.",
502
+ self.file_name,
421
503
  )
422
- self.zooming = False
423
504
  self.robot_in_room = last_room
505
+ self.zooming = False
424
506
  temp = {
425
507
  "x": robot_x,
426
508
  "y": robot_y,
427
509
  "angle": angle,
428
- "in_room": self.robot_in_room["room"] if self.robot_in_room else "unknown",
510
+ "in_room": last_room["room"] if last_room else "unknown",
429
511
  }
430
512
  return temp
431
513
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: valetudo-map-parser
3
- Version: 0.1.9b55
3
+ Version: 0.1.9b56
4
4
  Summary: A Python library to parse Valetudo map data returning a PIL Image object.
5
5
  License: Apache-2.0
6
6
  Author: Sandro Cantarella
@@ -17,11 +17,11 @@ valetudo_map_parser/hypfer_handler.py,sha256=wvkZt6MsUF0gkHZDAwiUgOOawkdvakRzYBV
17
17
  valetudo_map_parser/hypfer_rooms_handler.py,sha256=NkpOA6Gdq-2D3lLAxvtNuuWMvPXHxeMY2TO5RZLSHlU,22652
18
18
  valetudo_map_parser/map_data.py,sha256=zQKE8EzWxR0r0qyfD1QQq51T1wFrpcIeXtnpm92-LXQ,17743
19
19
  valetudo_map_parser/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- valetudo_map_parser/rand25_handler.py,sha256=VOBzx0AY61AggOkiX-P4c73MrPrQBGfsDQTEbVL1wCQ,18855
20
+ valetudo_map_parser/rand25_handler.py,sha256=GpY9R9EGWM06KYF4VQ1NxYw0idaJ8lZNkkA5wa0cr-c,22292
21
21
  valetudo_map_parser/reimg_draw.py,sha256=1q8LkNTPHEA9Tsapc_JnVw51kpPYNhaBU-KmHkefCQY,12507
22
22
  valetudo_map_parser/rooms_handler.py,sha256=YP8OLotBH-RXluv398l7TTT2zIBHJp91b8THWxl3NdI,17794
23
- valetudo_map_parser-0.1.9b55.dist-info/LICENSE,sha256=Lh-qBbuRV0-jiCIBhfV7NgdwFxQFOXH3BKOzK865hRs,10480
24
- valetudo_map_parser-0.1.9b55.dist-info/METADATA,sha256=Af2KutWU3EHPE8F1HNCWWKlIGKqr9bz1nTbC1-d3ZCo,3321
25
- valetudo_map_parser-0.1.9b55.dist-info/NOTICE.txt,sha256=5lTOuWiU9aiEnJ2go8sc7lTJ7ntMBx0g0GFnNrswCY4,2533
26
- valetudo_map_parser-0.1.9b55.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
27
- valetudo_map_parser-0.1.9b55.dist-info/RECORD,,
23
+ valetudo_map_parser-0.1.9b56.dist-info/LICENSE,sha256=Lh-qBbuRV0-jiCIBhfV7NgdwFxQFOXH3BKOzK865hRs,10480
24
+ valetudo_map_parser-0.1.9b56.dist-info/METADATA,sha256=LJXXb676sSJ9W7NGafpcJSF4CRGGhbg_lCyrzcxkge0,3321
25
+ valetudo_map_parser-0.1.9b56.dist-info/NOTICE.txt,sha256=5lTOuWiU9aiEnJ2go8sc7lTJ7ntMBx0g0GFnNrswCY4,2533
26
+ valetudo_map_parser-0.1.9b56.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
27
+ valetudo_map_parser-0.1.9b56.dist-info/RECORD,,