Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
*
*
* * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by
* * the European Commission - subsequent versions of the EUPL (the "Licence");
* * You may not use this work except in compliance with the Licence.
* * You may obtain a copy of the Licence at:
* *
* * https://joinup.ec.europa.eu/software/page/eupl
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the Licence is distributed on an "AS IS" basis,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the Licence for the specific language governing permissions and
* * limitations under the Licence.
*
*/

package org.entur.lamassu.cache.impl;

import org.entur.lamassu.model.entities.GeofencingZonesData;
import org.redisson.api.RMapCache;
import org.springframework.stereotype.Component;

@Component
public class GeofencingZonesDataCacheImpl extends EntityCacheImpl<GeofencingZonesData> {

protected GeofencingZonesDataCacheImpl(RMapCache<String, GeofencingZonesData> cache) {
super(cache);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.entur.lamassu.cache.impl.RedisUpdateContinuityCache;
import org.entur.lamassu.config.project.LamassuProjectInfoConfiguration;
import org.entur.lamassu.model.entities.GeofencingZones;
import org.entur.lamassu.model.entities.GeofencingZonesData;
import org.entur.lamassu.model.entities.PricingPlan;
import org.entur.lamassu.model.entities.Region;
import org.entur.lamassu.model.entities.Station;
Expand Down Expand Up @@ -43,6 +44,7 @@ public class RedissonCacheConfig {
public static final String STATION_CACHE_KEY = "stationCache";
public static final String REGION_CACHE_KEY = "regionCache";
public static final String GEOFENCING_ZONES_CACHE_KEY = "geofencingZonesCache";
public static final String GEOFENCING_ZONES_DATA_CACHE_KEY = "geofencingZonesDataCache";
public static final String VEHICLE_SPATIAL_INDEX_KEY = "vehicleSpatialIndex";
public static final String STATION_SPATIAL_INDEX_KEY = "stationSpatialIndex";
public static final String VALIDATION_REPORTS_CACHE_KEY = "validationReportsCache";
Expand Down Expand Up @@ -169,6 +171,15 @@ public RMapCache<String, GeofencingZones> geofencingZonesCache(
);
}

@Bean
public RMapCache<String, GeofencingZonesData> geofencingZonesDataCache(
RedissonClient redissonClient
) {
return redissonClient.getMapCache(
GEOFENCING_ZONES_DATA_CACHE_KEY + "_" + serializationVersion
);
}

@Bean
public RGeo<VehicleSpatialIndexId> vehicleSpatialIndex(RedissonClient redissonClient) {
return redissonClient.getGeo(VEHICLE_SPATIAL_INDEX_KEY + "_" + serializationVersion);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.entur.lamassu.cache.EntityReader;
import org.entur.lamassu.graphql.validation.QueryParameterValidator;
import org.entur.lamassu.model.entities.GeofencingZones;
import org.entur.lamassu.model.entities.GeofencingZonesData;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.stereotype.Controller;
Expand All @@ -13,13 +14,16 @@
public class GeofencingZonesQueryController {

private final EntityReader<GeofencingZones> geofencingZonesReader;
private final EntityReader<GeofencingZonesData> geofencingZonesDataReader;
private final QueryParameterValidator validationService;

public GeofencingZonesQueryController(
EntityReader<GeofencingZones> geofencingZonesReader,
EntityReader<GeofencingZonesData> geofencingZonesDataReader,
QueryParameterValidator validationService
) {
this.geofencingZonesReader = geofencingZonesReader;
this.geofencingZonesDataReader = geofencingZonesDataReader;
this.validationService = validationService;
}

Expand All @@ -31,4 +35,13 @@ public List<GeofencingZones> geofencingZones(@Argument List<String> systemIds) {
}
return geofencingZonesReader.getAll();
}

@QueryMapping
public List<GeofencingZonesData> geofencingZonesData(@Argument List<String> systemIds) {
validationService.validateSystems(systemIds);
if (systemIds != null && !systemIds.isEmpty()) {
return geofencingZonesDataReader.getAll(Set.copyOf(systemIds));
}
return geofencingZonesDataReader.getAll();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.entur.lamassu.cache.EntityCache;
import org.entur.lamassu.mapper.entitymapper.GeofencingZonesDataMapper;
import org.entur.lamassu.mapper.entitymapper.GeofencingZonesMapper;
import org.entur.lamassu.model.entities.GeofencingZones;
import org.entur.lamassu.model.entities.GeofencingZonesData;
import org.entur.lamassu.model.provider.FeedProvider;
import org.entur.lamassu.util.CacheUtil;
import org.mobilitydata.gbfs.v3_0.geofencing_zones.GBFSGeofencingZones;
Expand All @@ -34,34 +36,57 @@
public class GeofencingZonesUpdater {

private final EntityCache<GeofencingZones> geofencingZonesCache;
private final EntityCache<GeofencingZonesData> geofencingZonesDataCache;
private final GeofencingZonesMapper geofencingZonesMapper;
private final GeofencingZonesDataMapper geofencingZonesDataMapper;

@Autowired
public GeofencingZonesUpdater(
EntityCache<GeofencingZones> geofencingZonesCache,
GeofencingZonesMapper geofencingZonesMapper
EntityCache<GeofencingZonesData> geofencingZonesDataCache,
GeofencingZonesMapper geofencingZonesMapper,
GeofencingZonesDataMapper geofencingZonesDataMapper
) {
this.geofencingZonesCache = geofencingZonesCache;
this.geofencingZonesDataCache = geofencingZonesDataCache;
this.geofencingZonesMapper = geofencingZonesMapper;
this.geofencingZonesDataMapper = geofencingZonesDataMapper;
}

public void update(FeedProvider feedProvider, GBFSGeofencingZones feed) {
// Update legacy cache for backwards compatibility
var mapped = geofencingZonesMapper.map(
feed.getData().getGeofencingZones(),
feedProvider
);

// Update new optimized cache with global rules support
var mappedData = geofencingZonesDataMapper.map(feed, feedProvider);

var lastUpdated = feed.getLastUpdated();
var ttl = feed.getTtl();

var ttlSeconds = CacheUtil.getTtl(
(int) Instant.now().getEpochSecond(),
(int) (lastUpdated.getTime() / 100),
ttl,
3600
);

// Update legacy cache
geofencingZonesCache.updateAll(
Map.of(mapped.getId(), mapped),
CacheUtil.getTtl(
(int) Instant.now().getEpochSecond(),
(int) (lastUpdated.getTime() / 100),
ttl,
3600
),
ttlSeconds,
TimeUnit.SECONDS
);

// Update new optimized cache
if (mappedData != null) {
geofencingZonesDataCache.updateAll(
Map.of(mappedData.getId(), mappedData),
ttlSeconds,
TimeUnit.SECONDS
);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
*
*
* * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by
* * the European Commission - subsequent versions of the EUPL (the "Licence");
* * You may not use this work except in compliance with the Licence.
* * You may obtain a copy of the Licence at:
* *
* * https://joinup.ec.europa.eu/software/page/eupl
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the Licence is distributed on an "AS IS" basis,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the Licence for the specific language governing permissions and
* * limitations under the Licence.
*
*/

package org.entur.lamassu.mapper.entitymapper;

import com.mapbox.geojson.Point;
import com.mapbox.geojson.utils.PolylineUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.entur.lamassu.model.entities.GeofencingZones;
import org.entur.lamassu.model.entities.GeofencingZonesData;
import org.entur.lamassu.model.entities.MultiPolygon;
import org.entur.lamassu.model.provider.FeedProvider;
import org.mobilitydata.gbfs.v3_0.geofencing_zones.GBFSData;
import org.mobilitydata.gbfs.v3_0.geofencing_zones.GBFSFeature;
import org.mobilitydata.gbfs.v3_0.geofencing_zones.GBFSGeofencingZones;
import org.mobilitydata.gbfs.v3_0.geofencing_zones.GBFSGlobalRule;
import org.mobilitydata.gbfs.v3_0.geofencing_zones.GBFSName;
import org.mobilitydata.gbfs.v3_0.geofencing_zones.GBFSProperties;
import org.mobilitydata.gbfs.v3_0.geofencing_zones.GBFSRule;
import org.springframework.stereotype.Component;

@Component
public class GeofencingZonesDataMapper {

public GeofencingZonesData map(
GBFSGeofencingZones geofencingZonesResponse,
FeedProvider feedProvider
) {
if (geofencingZonesResponse == null || geofencingZonesResponse.getData() == null) {
return null;
}

var mapped = new GeofencingZonesData();
mapped.setSystemId(feedProvider.getSystemId());

GBFSData data = geofencingZonesResponse.getData();

// Map zones with optimized structure
mapped.setZones(mapZones(data, feedProvider.getLanguage()));

// Map global rules
mapped.setGlobalRules(mapGlobalRules(data.getGlobalRules()));

return mapped;
}

private List<GeofencingZonesData.GeofencingZone> mapZones(
GBFSData data,
String language
) {
if (
data.getGeofencingZones() == null || data.getGeofencingZones().getFeatures() == null
) {
return Collections.emptyList();
}

return data
.getGeofencingZones()
.getFeatures()
.stream()
.map(feature -> mapZone(feature, language))
.toList();
}

private GeofencingZonesData.GeofencingZone mapZone(
GBFSFeature feature,
String language
) {
var zone = new GeofencingZonesData.GeofencingZone();

// Map properties
if (feature.getProperties() != null) {
GBFSProperties properties = feature.getProperties();

// Map name
zone.setName(extractName(properties.getName(), language));

// Map time bounds
zone.setStart(
properties.getStart() != null ? properties.getStart().getTime() / 1000 : null
);
zone.setEnd(
properties.getEnd() != null ? properties.getEnd().getTime() / 1000 : null
);

// Map rules
zone.setRules(mapRules(properties.getRules()));
}

// Map and encode geometry as polyline
zone.setPolylineEncodedMultiPolygon(
encodeMultiPolygon(MultiPolygon.fromGeoJson(feature.getGeometry()))
);

return zone;
}

private String extractName(List<GBFSName> names, String language) {
if (names == null || names.isEmpty()) {
return null;
}

return names
.stream()
.filter(name -> name.getLanguage().equals(language))
.map(GBFSName::getText)
.findFirst()
.orElse(
// Fallback to first available name if language not found
names.get(0).getText()
);
}

private List<List<String>> encodeMultiPolygon(MultiPolygon multiPolygon) {
if (multiPolygon == null || multiPolygon.getCoordinates() == null) {
return Collections.emptyList();
}

return multiPolygon
.getCoordinates()
.stream()
.map(polygon ->
polygon
.stream()
.map(ring ->
ring
.stream()
.map(coords -> Point.fromLngLat(coords.get(0), coords.get(1)))
.toList()
)
.map(ring -> PolylineUtils.encode(ring, 6))
.toList()
)
.toList();
}

private List<GeofencingZones.Rule> mapRules(List<GBFSRule> rules) {
if (rules == null) {
return Collections.emptyList();
}
return rules.stream().map(this::mapRule).toList();
}

private List<GeofencingZones.Rule> mapGlobalRules(List<GBFSGlobalRule> globalRules) {
if (globalRules == null) {
return Collections.emptyList();
}
return globalRules.stream().map(this::mapGlobalRule).toList();
}

private GeofencingZones.Rule mapRule(GBFSRule rule) {
var mapped = new GeofencingZones.Rule();
mapped.setVehicleTypeIds(rule.getVehicleTypeIds());
mapped.setRideAllowed(rule.getRideStartAllowed() && rule.getRideEndAllowed());
mapped.setRideStartAllowed(rule.getRideStartAllowed());
mapped.setRideEndAllowed(rule.getRideEndAllowed());
mapped.setRideThroughAllowed(rule.getRideThroughAllowed());
mapped.setMaximumSpeedKph(
rule.getMaximumSpeedKph() != null ? rule.getMaximumSpeedKph() : null
);
mapped.setStationParking(rule.getStationParking());
return mapped;
}

private GeofencingZones.Rule mapGlobalRule(GBFSGlobalRule rule) {
var mapped = new GeofencingZones.Rule();
mapped.setVehicleTypeIds(rule.getVehicleTypeIds());
mapped.setRideAllowed(rule.getRideStartAllowed() && rule.getRideEndAllowed());
mapped.setRideStartAllowed(rule.getRideStartAllowed());
mapped.setRideEndAllowed(rule.getRideEndAllowed());
mapped.setRideThroughAllowed(rule.getRideThroughAllowed());
mapped.setMaximumSpeedKph(
rule.getMaximumSpeedKph() != null ? rule.getMaximumSpeedKph() : null
);
mapped.setStationParking(rule.getStationParking());
return mapped;
}
}
Loading