valetudo-map-parser 0.1.7__py3-none-any.whl → 0.1.9a1__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.
Files changed (28) hide show
  1. valetudo_map_parser/__init__.py +19 -12
  2. valetudo_map_parser/config/auto_crop.py +174 -116
  3. valetudo_map_parser/config/color_utils.py +105 -0
  4. valetudo_map_parser/config/colors.py +662 -13
  5. valetudo_map_parser/config/drawable.py +624 -279
  6. valetudo_map_parser/config/drawable_elements.py +292 -0
  7. valetudo_map_parser/config/enhanced_drawable.py +324 -0
  8. valetudo_map_parser/config/optimized_element_map.py +406 -0
  9. valetudo_map_parser/config/rand25_parser.py +42 -28
  10. valetudo_map_parser/config/room_outline.py +148 -0
  11. valetudo_map_parser/config/shared.py +29 -5
  12. valetudo_map_parser/config/types.py +102 -51
  13. valetudo_map_parser/config/utils.py +841 -0
  14. valetudo_map_parser/hypfer_draw.py +398 -132
  15. valetudo_map_parser/hypfer_handler.py +259 -241
  16. valetudo_map_parser/hypfer_rooms_handler.py +599 -0
  17. valetudo_map_parser/map_data.py +45 -64
  18. valetudo_map_parser/rand25_handler.py +429 -310
  19. valetudo_map_parser/reimg_draw.py +55 -74
  20. valetudo_map_parser/rooms_handler.py +470 -0
  21. valetudo_map_parser-0.1.9a1.dist-info/METADATA +93 -0
  22. valetudo_map_parser-0.1.9a1.dist-info/RECORD +27 -0
  23. {valetudo_map_parser-0.1.7.dist-info → valetudo_map_parser-0.1.9a1.dist-info}/WHEEL +1 -1
  24. valetudo_map_parser/images_utils.py +0 -398
  25. valetudo_map_parser-0.1.7.dist-info/METADATA +0 -23
  26. valetudo_map_parser-0.1.7.dist-info/RECORD +0 -20
  27. {valetudo_map_parser-0.1.7.dist-info → valetudo_map_parser-0.1.9a1.dist-info}/LICENSE +0 -0
  28. {valetudo_map_parser-0.1.7.dist-info → valetudo_map_parser-0.1.9a1.dist-info}/NOTICE.txt +0 -0
@@ -0,0 +1,406 @@
1
+ """
2
+ Optimized Element Map Generator.
3
+ Uses scipy for efficient element map generation and processing.
4
+ Version: 0.1.9
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import logging
10
+ import numpy as np
11
+ from scipy import ndimage
12
+
13
+ from .drawable_elements import DrawableElement, DrawingConfig
14
+ from .types import LOGGER
15
+
16
+
17
+ class OptimizedElementMapGenerator:
18
+ """Class for generating 2D element maps from JSON data with optimized performance.
19
+
20
+ This class creates a 2D array where each cell contains an integer code
21
+ representing the element at that position (floor, wall, room, etc.).
22
+ It uses scipy for efficient processing and supports sparse matrices for memory efficiency.
23
+ """
24
+
25
+ def __init__(self, drawing_config: DrawingConfig = None, shared_data=None):
26
+ """Initialize the optimized element map generator.
27
+
28
+ Args:
29
+ drawing_config: Optional drawing configuration for element properties
30
+ shared_data: Shared data object for accessing common resources
31
+ """
32
+ self.drawing_config = drawing_config or DrawingConfig()
33
+ self.shared = shared_data
34
+ self.element_map = None
35
+ self.element_map_shape = None
36
+ self.scale_info = None
37
+ self.file_name = (
38
+ getattr(shared_data, "file_name", "ElementMap")
39
+ if shared_data
40
+ else "ElementMap"
41
+ )
42
+
43
+ async def async_generate_from_json(self, json_data, existing_element_map=None):
44
+ """Generate a 2D element map from JSON data with optimized performance.
45
+
46
+ Args:
47
+ json_data: The JSON data from the vacuum
48
+ existing_element_map: Optional pre-created element map to populate
49
+
50
+ Returns:
51
+ numpy.ndarray: The 2D element map
52
+ """
53
+ if not self.shared:
54
+ LOGGER.warning("Shared data not provided, some features may not work.")
55
+ return None
56
+
57
+ # Use existing element map if provided
58
+ if existing_element_map is not None:
59
+ self.element_map = existing_element_map
60
+ return existing_element_map
61
+
62
+ # Detect JSON format
63
+ is_valetudo = "layers" in json_data and "pixelSize" in json_data
64
+ is_rand256 = "map_data" in json_data
65
+
66
+ if not (is_valetudo or is_rand256):
67
+ LOGGER.error("Unknown JSON format, cannot generate element map")
68
+ return None
69
+
70
+ if is_valetudo:
71
+ return await self._generate_valetudo_element_map(json_data)
72
+ elif is_rand256:
73
+ return await self._generate_rand256_element_map(json_data)
74
+
75
+ async def _generate_valetudo_element_map(self, json_data):
76
+ """Generate an element map from Valetudo format JSON data."""
77
+ # Get map dimensions from the JSON data
78
+ size_x = json_data["size"]["x"]
79
+ size_y = json_data["size"]["y"]
80
+ pixel_size = json_data["pixelSize"]
81
+
82
+ # Calculate downscale factor based on pixel size
83
+ # Standard pixel size is 5mm, so adjust accordingly
84
+ downscale_factor = max(1, pixel_size // 5 * 2) # More aggressive downscaling
85
+
86
+ # Calculate dimensions for the downscaled map
87
+ map_width = max(100, size_x // (pixel_size * downscale_factor))
88
+ map_height = max(100, size_y // (pixel_size * downscale_factor))
89
+
90
+ LOGGER.info(
91
+ "%s: Creating optimized element map with dimensions: %dx%d (downscale factor: %d)",
92
+ self.file_name,
93
+ map_width,
94
+ map_height,
95
+ downscale_factor,
96
+ )
97
+
98
+ # Create the element map at the reduced size
99
+ element_map = np.zeros((map_height, map_width), dtype=np.int32)
100
+ element_map[:] = DrawableElement.FLOOR
101
+
102
+ # Store scaling information for coordinate conversion
103
+ self.scale_info = {
104
+ "original_size": (size_x, size_y),
105
+ "map_size": (map_width, map_height),
106
+ "scale_factor": downscale_factor * pixel_size,
107
+ "pixel_size": pixel_size,
108
+ }
109
+
110
+ # Process layers at the reduced resolution
111
+ for layer in json_data.get("layers", []):
112
+ layer_type = layer.get("type")
113
+
114
+ # Process rooms (segments)
115
+ if layer_type == "segment":
116
+ # Get room ID
117
+ meta_data = layer.get("metaData", {})
118
+ segment_id = meta_data.get("segmentId")
119
+
120
+ if segment_id is not None:
121
+ # Convert segment_id to int if it's a string
122
+ segment_id_int = (
123
+ int(segment_id) if isinstance(segment_id, str) else segment_id
124
+ )
125
+ if 1 <= segment_id_int <= 15:
126
+ room_element = getattr(
127
+ DrawableElement, f"ROOM_{segment_id_int}", None
128
+ )
129
+
130
+ # Skip if room is disabled
131
+ if room_element is None or not self.drawing_config.is_enabled(
132
+ room_element
133
+ ):
134
+ continue
135
+
136
+ # Create a temporary high-resolution mask for this room
137
+ temp_mask = np.zeros(
138
+ (size_y // pixel_size, size_x // pixel_size), dtype=np.uint8
139
+ )
140
+
141
+ # Process pixels for this room
142
+ compressed_pixels = layer.get("compressedPixels", [])
143
+ if compressed_pixels:
144
+ # Process in chunks of 3 (x, y, count)
145
+ for i in range(0, len(compressed_pixels), 3):
146
+ if i + 2 < len(compressed_pixels):
147
+ x = compressed_pixels[i]
148
+ y = compressed_pixels[i + 1]
149
+ count = compressed_pixels[i + 2]
150
+
151
+ # Set pixels in the high-resolution mask
152
+ for j in range(count):
153
+ px = x + j
154
+ if (
155
+ 0 <= y < temp_mask.shape[0]
156
+ and 0 <= px < temp_mask.shape[1]
157
+ ):
158
+ temp_mask[y, px] = 1
159
+
160
+ # Use scipy to downsample the mask efficiently
161
+ # This preserves the room shape better than simple decimation
162
+ downsampled_mask = ndimage.zoom(
163
+ temp_mask,
164
+ (
165
+ map_height / temp_mask.shape[0],
166
+ map_width / temp_mask.shape[1],
167
+ ),
168
+ order=0, # Nearest neighbor interpolation
169
+ )
170
+
171
+ # Apply the downsampled mask to the element map
172
+ element_map[downsampled_mask > 0] = room_element
173
+
174
+ # Clean up
175
+ del temp_mask, downsampled_mask
176
+
177
+ # Process walls similarly
178
+ elif layer_type == "wall" and self.drawing_config.is_enabled(
179
+ DrawableElement.WALL
180
+ ):
181
+ # Create a temporary high-resolution mask for walls
182
+ temp_mask = np.zeros(
183
+ (size_y // pixel_size, size_x // pixel_size), dtype=np.uint8
184
+ )
185
+
186
+ # Process compressed pixels for walls
187
+ compressed_pixels = layer.get("compressedPixels", [])
188
+ if compressed_pixels:
189
+ # Process in chunks of 3 (x, y, count)
190
+ for i in range(0, len(compressed_pixels), 3):
191
+ if i + 2 < len(compressed_pixels):
192
+ x = compressed_pixels[i]
193
+ y = compressed_pixels[i + 1]
194
+ count = compressed_pixels[i + 2]
195
+
196
+ # Set pixels in the high-resolution mask
197
+ for j in range(count):
198
+ px = x + j
199
+ if (
200
+ 0 <= y < temp_mask.shape[0]
201
+ and 0 <= px < temp_mask.shape[1]
202
+ ):
203
+ temp_mask[y, px] = 1
204
+
205
+ # Use scipy to downsample the mask efficiently
206
+ downsampled_mask = ndimage.zoom(
207
+ temp_mask,
208
+ (map_height / temp_mask.shape[0], map_width / temp_mask.shape[1]),
209
+ order=0,
210
+ )
211
+
212
+ # Apply the downsampled mask to the element map
213
+ # Only overwrite floor pixels, not room pixels
214
+ wall_mask = (downsampled_mask > 0) & (
215
+ element_map == DrawableElement.FLOOR
216
+ )
217
+ element_map[wall_mask] = DrawableElement.WALL
218
+
219
+ # Clean up
220
+ del temp_mask, downsampled_mask
221
+
222
+ # Store the element map
223
+ self.element_map = element_map
224
+ self.element_map_shape = element_map.shape
225
+
226
+ LOGGER.info(
227
+ "%s: Element map generation complete with shape: %s",
228
+ self.file_name,
229
+ element_map.shape,
230
+ )
231
+ return element_map
232
+
233
+ async def _generate_rand256_element_map(self, json_data):
234
+ """Generate an element map from Rand256 format JSON data."""
235
+ # Get map dimensions from the Rand256 JSON data
236
+ map_data = json_data["map_data"]
237
+ size_x = map_data["dimensions"]["width"]
238
+ size_y = map_data["dimensions"]["height"]
239
+
240
+ # Calculate downscale factor
241
+ downscale_factor = max(
242
+ 1, min(size_x, size_y) // 500
243
+ ) # Target ~500px in smallest dimension
244
+
245
+ # Calculate dimensions for the downscaled map
246
+ map_width = max(100, size_x // downscale_factor)
247
+ map_height = max(100, size_y // downscale_factor)
248
+
249
+ LOGGER.info(
250
+ "%s: Creating optimized Rand256 element map with dimensions: %dx%d (downscale factor: %d)",
251
+ self.file_name,
252
+ map_width,
253
+ map_height,
254
+ downscale_factor,
255
+ )
256
+
257
+ # Create the element map at the reduced size
258
+ element_map = np.zeros((map_height, map_width), dtype=np.int32)
259
+ element_map[:] = DrawableElement.FLOOR
260
+
261
+ # Store scaling information for coordinate conversion
262
+ self.scale_info = {
263
+ "original_size": (size_x, size_y),
264
+ "map_size": (map_width, map_height),
265
+ "scale_factor": downscale_factor,
266
+ "pixel_size": 1, # Rand256 uses 1:1 pixel mapping
267
+ }
268
+
269
+ # Process rooms
270
+ if "rooms" in map_data and map_data["rooms"]:
271
+ for room in map_data["rooms"]:
272
+ # Get room ID and check if it's enabled
273
+ room_id_int = room["id"]
274
+
275
+ # Get room element code (ROOM_1, ROOM_2, etc.)
276
+ room_element = None
277
+ if 0 < room_id_int <= 15:
278
+ room_element = getattr(DrawableElement, f"ROOM_{room_id_int}", None)
279
+
280
+ # Skip if room is disabled
281
+ if room_element is None or not self.drawing_config.is_enabled(
282
+ room_element
283
+ ):
284
+ continue
285
+
286
+ if "coordinates" in room:
287
+ # Create a high-resolution mask for this room
288
+ temp_mask = np.zeros((size_y, size_x), dtype=np.uint8)
289
+
290
+ # Fill the mask with room coordinates
291
+ for coord in room["coordinates"]:
292
+ x, y = coord
293
+ if 0 <= y < size_y and 0 <= x < size_x:
294
+ temp_mask[y, x] = 1
295
+
296
+ # Use scipy to downsample the mask efficiently
297
+ downsampled_mask = ndimage.zoom(
298
+ temp_mask,
299
+ (map_height / size_y, map_width / size_x),
300
+ order=0, # Nearest neighbor interpolation
301
+ )
302
+
303
+ # Apply the downsampled mask to the element map
304
+ element_map[downsampled_mask > 0] = room_element
305
+
306
+ # Clean up
307
+ del temp_mask, downsampled_mask
308
+
309
+ # Process walls
310
+ if (
311
+ "walls" in map_data
312
+ and map_data["walls"]
313
+ and self.drawing_config.is_enabled(DrawableElement.WALL)
314
+ ):
315
+ # Create a high-resolution mask for walls
316
+ temp_mask = np.zeros((size_y, size_x), dtype=np.uint8)
317
+
318
+ # Fill the mask with wall coordinates
319
+ for coord in map_data["walls"]:
320
+ x, y = coord
321
+ if 0 <= y < size_y and 0 <= x < size_x:
322
+ temp_mask[y, x] = 1
323
+
324
+ # Use scipy to downsample the mask efficiently
325
+ downsampled_mask = ndimage.zoom(
326
+ temp_mask, (map_height / size_y, map_width / size_x), order=0
327
+ )
328
+
329
+ # Apply the downsampled mask to the element map
330
+ # Only overwrite floor pixels, not room pixels
331
+ wall_mask = (downsampled_mask > 0) & (element_map == DrawableElement.FLOOR)
332
+ element_map[wall_mask] = DrawableElement.WALL
333
+
334
+ # Clean up
335
+ del temp_mask, downsampled_mask
336
+
337
+ # Store the element map
338
+ self.element_map = element_map
339
+ self.element_map_shape = element_map.shape
340
+
341
+ LOGGER.info(
342
+ "%s: Rand256 element map generation complete with shape: %s",
343
+ self.file_name,
344
+ element_map.shape,
345
+ )
346
+ return element_map
347
+
348
+ def map_to_element_coordinates(self, x, y):
349
+ """Convert map coordinates to element map coordinates."""
350
+ if not hasattr(self, "scale_info"):
351
+ return x, y
352
+
353
+ scale = self.scale_info["scale_factor"]
354
+ return int(x / scale), int(y / scale)
355
+
356
+ def element_to_map_coordinates(self, x, y):
357
+ """Convert element map coordinates to map coordinates."""
358
+ if not hasattr(self, "scale_info"):
359
+ return x, y
360
+
361
+ scale = self.scale_info["scale_factor"]
362
+ return int(x * scale), int(y * scale)
363
+
364
+ def get_element_at_position(self, x, y):
365
+ """Get the element at the specified position."""
366
+ if not hasattr(self, "element_map") or self.element_map is None:
367
+ return None
368
+
369
+ if not (
370
+ 0 <= y < self.element_map.shape[0] and 0 <= x < self.element_map.shape[1]
371
+ ):
372
+ return None
373
+
374
+ return self.element_map[y, x]
375
+
376
+ def get_room_at_position(self, x, y):
377
+ """Get the room ID at a specific position, or None if not a room."""
378
+ element_code = self.get_element_at_position(x, y)
379
+ if element_code is None:
380
+ return None
381
+
382
+ # Check if it's a room (codes 101-115)
383
+ if 101 <= element_code <= 115:
384
+ return element_code
385
+ return None
386
+
387
+ def get_element_name(self, element_code):
388
+ """Get the name of the element from its code."""
389
+ if element_code is None:
390
+ return "NONE"
391
+
392
+ # Check if it's a room
393
+ if element_code >= 100:
394
+ room_number = element_code - 100
395
+ return f"ROOM_{room_number}"
396
+
397
+ # Check standard elements
398
+ for name, code in vars(DrawableElement).items():
399
+ if (
400
+ not name.startswith("_")
401
+ and isinstance(code, int)
402
+ and code == element_code
403
+ ):
404
+ return name
405
+
406
+ return f"UNKNOWN_{element_code}"
@@ -5,10 +5,11 @@ Version: v2024.08.2
5
5
  - Additional functions are to get in our image_handler the images datas.
6
6
  """
7
7
 
8
- from enum import Enum
9
8
  import math
10
9
  import struct
11
- from typing import Dict, List, Optional, Callable, TypeVar, Any
10
+ from enum import Enum
11
+ from typing import Any, Callable, Dict, List, Optional, TypeVar
12
+
12
13
 
13
14
  _CallableT = TypeVar("_CallableT", bound=Callable[..., Any])
14
15
 
@@ -162,7 +163,7 @@ class RRMapParser:
162
163
  )
163
164
  if segment_type == 0:
164
165
  continue
165
- elif segment_type == 1 and pixels:
166
+ if segment_type == 1 and pixels:
166
167
  parameters["pixels"]["walls"].append(i)
167
168
  else:
168
169
  s = (
@@ -266,6 +267,18 @@ class RRMapParser:
266
267
  parsed_map_data = {}
267
268
  blocks = self.parse_block(map_buf, 0x14, None, pixels)
268
269
 
270
+ self._parse_image_data(parsed_map_data, blocks)
271
+ self._parse_charger_data(parsed_map_data, blocks)
272
+ self._parse_robot_data(parsed_map_data, blocks)
273
+ self._parse_zones_data(parsed_map_data, blocks)
274
+ self._parse_virtual_walls_data(parsed_map_data, blocks)
275
+ self._parse_misc_data(parsed_map_data, blocks)
276
+
277
+ return parsed_map_data
278
+
279
+ @staticmethod
280
+ def _parse_image_data(parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]):
281
+ """Parse image-related data."""
269
282
  if RRMapParser.Types.IMAGE.value in blocks:
270
283
  parsed_map_data["image"] = blocks[RRMapParser.Types.IMAGE.value]
271
284
  for item in [
@@ -290,28 +303,27 @@ class RRMapParser:
290
303
  - parsed_map_data[item["path"]]["points"][-2][0],
291
304
  )
292
305
  )
306
+
307
+ @staticmethod
308
+ def _parse_charger_data(parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]):
309
+ """Parse charger location data."""
293
310
  if RRMapParser.Types.CHARGER_LOCATION.value in blocks:
294
311
  charger = blocks[RRMapParser.Types.CHARGER_LOCATION.value]["position"]
295
- # Assume no transformation needed here
296
312
  parsed_map_data["charger"] = charger
297
313
 
314
+ @staticmethod
315
+ def _parse_robot_data(parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]):
316
+ """Parse robot position data."""
298
317
  if RRMapParser.Types.ROBOT_POSITION.value in blocks:
299
318
  robot = blocks[RRMapParser.Types.ROBOT_POSITION.value]["position"]
300
319
  rob_angle = blocks[RRMapParser.Types.ROBOT_POSITION.value]["angle"]
301
- # Assume no transformation needed here
302
320
  parsed_map_data["robot"] = robot
303
321
  parsed_map_data["robot_angle"] = rob_angle
304
322
 
305
- if RRMapParser.Types.GOTO_TARGET.value in blocks:
306
- parsed_map_data["goto_target"] = blocks[
307
- RRMapParser.Types.GOTO_TARGET.value
308
- ]["position"]
309
- # Assume no transformation needed here
310
-
323
+ @staticmethod
324
+ def _parse_zones_data(parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]):
325
+ """Parse zones and forbidden zones data."""
311
326
  if RRMapParser.Types.CURRENTLY_CLEANED_ZONES.value in blocks:
312
- parsed_map_data["currently_cleaned_zones"] = blocks[
313
- RRMapParser.Types.CURRENTLY_CLEANED_ZONES.value
314
- ]
315
327
  parsed_map_data["currently_cleaned_zones"] = [
316
328
  [
317
329
  zone[0],
@@ -319,13 +331,10 @@ class RRMapParser:
319
331
  zone[2],
320
332
  RRMapParser.Tools.DIMENSION_MM - zone[3],
321
333
  ]
322
- for zone in parsed_map_data["currently_cleaned_zones"]
334
+ for zone in blocks[RRMapParser.Types.CURRENTLY_CLEANED_ZONES.value]
323
335
  ]
324
336
 
325
337
  if RRMapParser.Types.FORBIDDEN_ZONES.value in blocks:
326
- parsed_map_data["forbidden_zones"] = blocks[
327
- RRMapParser.Types.FORBIDDEN_ZONES.value
328
- ]
329
338
  parsed_map_data["forbidden_zones"] = [
330
339
  [
331
340
  zone[0],
@@ -337,13 +346,15 @@ class RRMapParser:
337
346
  zone[6],
338
347
  RRMapParser.Tools.DIMENSION_MM - zone[7],
339
348
  ]
340
- for zone in parsed_map_data["forbidden_zones"]
349
+ for zone in blocks[RRMapParser.Types.FORBIDDEN_ZONES.value]
341
350
  ]
342
351
 
352
+ @staticmethod
353
+ def _parse_virtual_walls_data(
354
+ parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]
355
+ ):
356
+ """Parse virtual walls data."""
343
357
  if RRMapParser.Types.VIRTUAL_WALLS.value in blocks:
344
- parsed_map_data["virtual_walls"] = blocks[
345
- RRMapParser.Types.VIRTUAL_WALLS.value
346
- ]
347
358
  parsed_map_data["virtual_walls"] = [
348
359
  [
349
360
  wall[0],
@@ -351,18 +362,18 @@ class RRMapParser:
351
362
  wall[2],
352
363
  RRMapParser.Tools.DIMENSION_MM - wall[3],
353
364
  ]
354
- for wall in parsed_map_data["virtual_walls"]
365
+ for wall in blocks[RRMapParser.Types.VIRTUAL_WALLS.value]
355
366
  ]
356
367
 
368
+ @staticmethod
369
+ def _parse_misc_data(parsed_map_data: Dict[str, Any], blocks: Dict[int, Any]):
370
+ """Parse miscellaneous data like cleaned blocks and mop zones."""
357
371
  if RRMapParser.Types.CURRENTLY_CLEANED_BLOCKS.value in blocks:
358
372
  parsed_map_data["currently_cleaned_blocks"] = blocks[
359
373
  RRMapParser.Types.CURRENTLY_CLEANED_BLOCKS.value
360
374
  ]
361
375
 
362
376
  if RRMapParser.Types.FORBIDDEN_MOP_ZONES.value in blocks:
363
- parsed_map_data["forbidden_mop_zones"] = blocks[
364
- RRMapParser.Types.FORBIDDEN_MOP_ZONES.value
365
- ]
366
377
  parsed_map_data["forbidden_mop_zones"] = [
367
378
  [
368
379
  zone[0],
@@ -374,10 +385,13 @@ class RRMapParser:
374
385
  zone[6],
375
386
  RRMapParser.Tools.DIMENSION_MM - zone[7],
376
387
  ]
377
- for zone in parsed_map_data["forbidden_mop_zones"]
388
+ for zone in blocks[RRMapParser.Types.FORBIDDEN_MOP_ZONES.value]
378
389
  ]
379
390
 
380
- return parsed_map_data
391
+ if RRMapParser.Types.GOTO_TARGET.value in blocks:
392
+ parsed_map_data["goto_target"] = blocks[
393
+ RRMapParser.Types.GOTO_TARGET.value
394
+ ]["position"]
381
395
 
382
396
  def parse_data(
383
397
  self, payload: Optional[bytes] = None, pixels: bool = False