Skip to content

Comparison Reference

check_segment_bound_overlap(reference_segments, segments)

Check if segments are within the bounds of a reference segment

Parameters:

Name Type Description Default
reference_segments GPXTrackSegment

Segment defining the bounds inside which the other segments should be contained

required
segments list[GPXTrackSegment]

Segments to be checked

required

Returns:

Type Description
list[bool]

List of bools specifying if the segments are inside the refence bounds

Source code in geo_track_analyzer/compare.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def check_segment_bound_overlap(
    reference_segments: GPXTrackSegment, segments: list[GPXTrackSegment]
) -> list[bool]:
    """Check if segments are within the bounds of a reference segment

    :param reference_segments: Segment defining the bounds inside which the other
        segments should be contained
    :param segments: Segments to be checked

    :return: List of bools specifying if the segments are inside the refence bounds
    """
    reference_bounds = reference_segments.get_bounds()

    check_bounds(reference_bounds)

    res = []

    for segment in segments:
        bounds = segment.get_bounds()

        check_bounds(bounds)

        res.append(
            bounds.min_latitude < reference_bounds.max_latitude  # type: ignore
            and bounds.min_longitude < reference_bounds.max_latitude  # type: ignore
            and bounds.max_latitude > reference_bounds.min_latitude  # type: ignore
            and bounds.max_longitude > reference_bounds.min_longitude  # type: ignore
        )

    return res

get_segment_overlap(base_segment, match_segment, grid_width, max_queue_normalize=5, allow_points_outside_bounds=5, overlap_threshold=0.75)

Compare the tracks of two segements and caclulate the overlap.

Parameters:

Name Type Description Default
base_segment GPXTrackSegment

Base segement in which the match segment should be found

required
match_segment GPXTrackSegment

Other segmeent that should be found in the base segement

required
grid_width float

Width (in meters) of the grid that will be filled to estimate the overalp.

required
max_queue_normalize int

Minimum number of successive points in the segment between two points falling into same plate bin.

5
allow_points_outside_bounds int

Number of points between sub segments allowed for merging the segments.

5
overlap_threshold float

Minimum overlap required to return the overlap data.

0.75

Returns:

Type Description
list[SegmentOverlap]

list of SegmentOverlap objects.

Source code in geo_track_analyzer/compare.py
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
def get_segment_overlap(
    base_segment: GPXTrackSegment,
    match_segment: GPXTrackSegment,
    grid_width: float,
    max_queue_normalize: int = 5,
    allow_points_outside_bounds: int = 5,
    overlap_threshold: float = 0.75,
) -> list[SegmentOverlap]:
    """Compare the tracks of two segements and caclulate the overlap.

    :param base_segment: Base segement in which the match segment should be found
    :param match_segment: Other segmeent that should be found in the base segement
    :param grid_width: Width (in meters) of the grid that will be filled to estimate
                       the overalp.
    :param max_queue_normalize: Minimum number of successive points in the segment
                                between two points falling into same plate bin.
    :param allow_points_outside_bounds: Number of points between sub segments allowed
                                        for merging the segments.
    :param overlap_threshold: Minimum overlap required to return the overlap data.

    :return: list of SegmentOverlap objects.
    """
    bounds_match = match_segment.get_bounds()

    check_bounds(bounds_match)

    cropped_base_segment = crop_segment_to_bounds(
        base_segment,
        bounds_match.min_latitude,  # type: ignore
        bounds_match.min_longitude,  # type: ignore
        bounds_match.max_latitude,  # type: ignore
        bounds_match.max_longitude,  # type: ignore
    )

    plate_base = convert_segment_to_plate(
        cropped_base_segment,
        grid_width,
        bounds_match.min_latitude,  # type: ignore
        bounds_match.min_longitude,  # type: ignore
        bounds_match.max_latitude,  # type: ignore
        bounds_match.max_longitude,  # type: ignore
        True,
        max_queue_normalize,
    )

    plate_match = convert_segment_to_plate(
        match_segment,
        grid_width,
        bounds_match.min_latitude,  # type: ignore
        bounds_match.min_longitude,  # type: ignore
        bounds_match.max_latitude,  # type: ignore
        bounds_match.max_longitude,  # type: ignore
        True,
        max_queue_normalize,
    )

    # Check if the match segment appears muzltiple times in the base segemnt
    if plate_base.max() > 1:
        logger.debug(
            "Multiple occurances of points within match bounds in base segment"
        )
        base_points_in_bounds = get_points_inside_bounds(
            base_segment,
            bounds_match.min_latitude,  # type: ignore
            bounds_match.min_longitude,  # type: ignore
            bounds_match.max_latitude,  # type: ignore
            bounds_match.max_longitude,  # type: ignore
        )

        id_ranges_in_bounds = _extract_ranges(
            base_points_in_bounds, allow_points_outside_bounds
        )

        sub_segments = split_segment_by_id(base_segment, id_ranges_in_bounds)
        sub_segment_overlaps = []
        for i_sub_segment, (sub_segment, id_sub_segment) in enumerate(
            zip(sub_segments, id_ranges_in_bounds)
        ):
            logger.debug(
                "Processing overlap in sub-plate %s/%s",
                i_sub_segment + 1,
                len(sub_segments),
            )
            sub_plate = convert_segment_to_plate(
                sub_segment,
                grid_width,
                bounds_match.min_latitude,  # type: ignore
                bounds_match.min_longitude,  # type: ignore
                bounds_match.max_latitude,  # type: ignore
                bounds_match.max_longitude,  # type: ignore
                True,
                max_queue_normalize,
            )
            sub_segment_overlap = _calc_plate_overlap(
                base_segment=sub_segment,
                plate_base=sub_plate,
                match_segment=match_segment,
                plate_match=plate_match,
            )

            subseg_start, _ = id_sub_segment
            sub_segment_overlap.start_idx += subseg_start
            sub_segment_overlap.end_idx += subseg_start

            if sub_segment_overlap.overlap >= overlap_threshold:
                sub_segment_overlaps.append(sub_segment_overlap)

        return sorted(sub_segment_overlaps, key=lambda x: x.overlap, reverse=True)
    else:
        logger.debug("Processing overlap in plate")
        segment_overlap = _calc_plate_overlap(
            base_segment=base_segment,
            plate_base=plate_base,
            match_segment=match_segment,
            plate_match=plate_match,
        )
        if segment_overlap.overlap >= overlap_threshold:
            return [segment_overlap]
        else:
            return []

convert_segment_to_plate(segment, gird_width, bounds_min_latitude, bounds_min_longitude, bounds_max_latitude, bounds_max_longitude, normalize=False, max_queue_normalize=5)

Takes a GPXSegement and fills bins of a 2D array (called plate) with the passed bin width. Bins will start at the min latitude and longited values.

Parameters:

Name Type Description Default
segment GPXTrackSegment

The GPXPoints of the Segment will be filled into the plate

required
gird_width float

Width (in meters) of the grid

required
bounds_min_latitude float

Minimum latitude of the grid

required
bounds_min_longitude float

Minimum longitude of the grid

required
bounds_max_latitude float

Maximum latitude of the gtid. Bins may end with larger values than passed here dependeing on the grid width

required
bounds_max_longitude float

Maximum longitude of the grid. Bins may end with larger values than passed here dependeing on the grid width

required
normalize bool

If True, successive points (defined by the max_queue_normalize) will not change the values in a bin. This means that each bin values should have the value 1 except there is overlap with points later in the track. To decide this the previous max_queue_normalize points will be considered. So this value dependes on the chosen gridwidth.

False
max_queue_normalize int

Number of previous bins considered when normalize is set to true.

5

Returns:

Type Description
ndarray

2DArray representing the plate.

Source code in geo_track_analyzer/compare.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
def convert_segment_to_plate(
    segment: GPXTrackSegment,
    gird_width: float,
    bounds_min_latitude: float,
    bounds_min_longitude: float,
    bounds_max_latitude: float,
    bounds_max_longitude: float,
    normalize: bool = False,
    max_queue_normalize: int = 5,
) -> np.ndarray:
    """
    Takes a GPXSegement and fills bins of a 2D array (called plate) with the passed
    bin width. Bins will start at the min latitude and longited values.

    :param segment: The GPXPoints of the Segment will be filled into the plate
    :param gird_width: Width (in meters) of the grid
    :param bounds_min_latitude: Minimum latitude of the grid
    :param bounds_min_longitude: Minimum longitude of the grid
    :param bounds_max_latitude: Maximum latitude of the gtid. Bins may end with larger
                                values than passed here dependeing on the grid width
    :param bounds_max_longitude: Maximum longitude of the grid. Bins may end with larger
                                values than passed here dependeing on the grid width
    :param normalize: If True, successive points (defined by the max_queue_normalize)
                      will not change the values in a bin. This means that each bin
                      values should have the value 1 except there is overlap with
                      points later in the track. To decide this the previous
                      max_queue_normalize points will be considered. So this value
                      dependes on the chosen gridwidth.
    :param max_queue_normalize: Number of previous bins considered when normalize is
                                set to true.

    :return: 2DArray representing the plate.
    """
    bins_latitude, bins_longitude = derive_plate_bins(
        gird_width,
        bounds_min_latitude,
        bounds_min_longitude,
        bounds_max_latitude,
        bounds_max_longitude,
    )

    _lat_bins = np.array([b[0] for b in bins_latitude])
    _long_bins = np.array([b[1] for b in bins_longitude])

    lats, longs = [], []
    for point in segment.points:
        lats.append(point.latitude)
        longs.append(point.longitude)

    # np.digitize starts with 1. We want 0 as first bin
    segment_lat_bins = np.digitize(lats, _lat_bins) - 1
    segment_long_bins = np.digitize(longs, _long_bins) - 1

    plate = np.zeros(shape=(len(bins_latitude), len(bins_longitude)))

    prev_bins = deque(maxlen=max_queue_normalize)  # type: ignore

    for lat, long in zip(segment_lat_bins, segment_long_bins):
        if normalize:
            if any((lat, long) == prev_bin for prev_bin in prev_bins):
                continue
            prev_bins.append((lat, long))
            plate[lat, long] += 1
        else:
            plate[lat, long] += 1

    return np.flip(plate, axis=0)