mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 16:06:15 +00:00
Merge pull request #6667 from gdicristofaro/7249-geolocationAreasTracks
7249 geolocation areas tracks
This commit is contained in:
commit
06ae8a07c6
@ -31,7 +31,6 @@ import java.util.concurrent.BlockingQueue;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
|
||||||
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
|
import org.sleuthkit.autopsy.datasourcesummary.datamodel.SleuthkitCaseProvider.SleuthkitCaseProviderException;
|
||||||
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultArtifactUpdateGovernor;
|
import org.sleuthkit.autopsy.datasourcesummary.uiutils.DefaultArtifactUpdateGovernor;
|
||||||
import org.sleuthkit.autopsy.geolocation.AbstractWaypointFetcher;
|
import org.sleuthkit.autopsy.geolocation.AbstractWaypointFetcher;
|
||||||
@ -47,6 +46,7 @@ import org.sleuthkit.datamodel.DataSource;
|
|||||||
* Gathers summary data about Geolocation information for a data source.
|
* Gathers summary data about Geolocation information for a data source.
|
||||||
*/
|
*/
|
||||||
public class GeolocationSummary implements DefaultArtifactUpdateGovernor {
|
public class GeolocationSummary implements DefaultArtifactUpdateGovernor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A count of hits for a particular city.
|
* A count of hits for a particular city.
|
||||||
*/
|
*/
|
||||||
@ -169,6 +169,53 @@ public class GeolocationSummary implements DefaultArtifactUpdateGovernor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Carries data retrieved from the Geolocation API to be processed for
|
||||||
|
* closest cities.
|
||||||
|
*/
|
||||||
|
private static class GeoResult {
|
||||||
|
|
||||||
|
private final Set<MapWaypoint> mapWaypoints;
|
||||||
|
private final List<Set<MapWaypoint>> tracks;
|
||||||
|
private final List<Set<MapWaypoint>> areas;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main constructor.
|
||||||
|
*
|
||||||
|
* @param mapWaypoints The way points found for the data source.
|
||||||
|
* @param tracks A list of sets where each set is a track in the data
|
||||||
|
* source.
|
||||||
|
* @param areas A list of areas where each set is an area in the data
|
||||||
|
* source.
|
||||||
|
*/
|
||||||
|
private GeoResult(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks, List<Set<MapWaypoint>> areas) {
|
||||||
|
this.mapWaypoints = mapWaypoints;
|
||||||
|
this.tracks = tracks;
|
||||||
|
this.areas = areas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The way points found for the data source.
|
||||||
|
*/
|
||||||
|
private Set<MapWaypoint> getMapWaypoints() {
|
||||||
|
return mapWaypoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A list of sets where each set is a track in the data source.
|
||||||
|
*/
|
||||||
|
private List<Set<MapWaypoint>> getTracks() {
|
||||||
|
return tracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A list of areas where each set is an area in the data source.
|
||||||
|
*/
|
||||||
|
private List<Set<MapWaypoint>> getAreas() {
|
||||||
|
return areas;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// taken from GeoFilterPanel: all of the GPS artifact types.
|
// taken from GeoFilterPanel: all of the GPS artifact types.
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
private static final List<ARTIFACT_TYPE> GPS_ARTIFACT_TYPES = Arrays.asList(
|
private static final List<ARTIFACT_TYPE> GPS_ARTIFACT_TYPES = Arrays.asList(
|
||||||
@ -178,7 +225,8 @@ public class GeolocationSummary implements DefaultArtifactUpdateGovernor {
|
|||||||
BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH,
|
BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH,
|
||||||
BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK,
|
BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACK,
|
||||||
BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT,
|
BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT,
|
||||||
BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF
|
BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF,
|
||||||
|
BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_AREA
|
||||||
);
|
);
|
||||||
|
|
||||||
// all GPS types
|
// all GPS types
|
||||||
@ -186,8 +234,11 @@ public class GeolocationSummary implements DefaultArtifactUpdateGovernor {
|
|||||||
.map(artifactType -> artifactType.getTypeID())
|
.map(artifactType -> artifactType.getTypeID())
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
private static final Pair<Integer, Integer> EMPTY_COUNT = Pair.of(0, 0);
|
||||||
|
|
||||||
|
private static final long DAY_SECS = 24 * 60 * 60;
|
||||||
|
|
||||||
private final SleuthkitCaseProvider provider;
|
private final SleuthkitCaseProvider provider;
|
||||||
private final java.util.logging.Logger logger;
|
|
||||||
private final SupplierWithException<ClosestCityMapper, IOException> cityMapper;
|
private final SupplierWithException<ClosestCityMapper, IOException> cityMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -208,7 +259,7 @@ public class GeolocationSummary implements DefaultArtifactUpdateGovernor {
|
|||||||
* Default constructor.
|
* Default constructor.
|
||||||
*/
|
*/
|
||||||
public GeolocationSummary() {
|
public GeolocationSummary() {
|
||||||
this(() -> ClosestCityMapper.getInstance(), SleuthkitCaseProvider.DEFAULT, Logger.getLogger(GeolocationSummary.class.getName()));
|
this(() -> ClosestCityMapper.getInstance(), SleuthkitCaseProvider.DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -217,12 +268,10 @@ public class GeolocationSummary implements DefaultArtifactUpdateGovernor {
|
|||||||
* @param cityMapper A means of acquiring a ClosestCityMapper that can throw
|
* @param cityMapper A means of acquiring a ClosestCityMapper that can throw
|
||||||
* an IOException.
|
* an IOException.
|
||||||
* @param provider A means of acquiring a SleuthkitCaseProvider.
|
* @param provider A means of acquiring a SleuthkitCaseProvider.
|
||||||
* @param logger The logger.
|
|
||||||
*/
|
*/
|
||||||
public GeolocationSummary(SupplierWithException<ClosestCityMapper, IOException> cityMapper, SleuthkitCaseProvider provider, java.util.logging.Logger logger) {
|
public GeolocationSummary(SupplierWithException<ClosestCityMapper, IOException> cityMapper, SleuthkitCaseProvider provider) {
|
||||||
this.cityMapper = cityMapper;
|
this.cityMapper = cityMapper;
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.logger = logger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -256,8 +305,6 @@ public class GeolocationSummary implements DefaultArtifactUpdateGovernor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Pair<Integer, Integer> EMPTY_COUNT = Pair.of(0, 0);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Based on a set of waypoints, determines the count of total waypoints and
|
* Based on a set of waypoints, determines the count of total waypoints and
|
||||||
* a total of waypoints whose time stamp is greater than or equal to
|
* a total of waypoints whose time stamp is greater than or equal to
|
||||||
@ -268,31 +315,119 @@ public class GeolocationSummary implements DefaultArtifactUpdateGovernor {
|
|||||||
* @return A pair where the left value is the total count of way points and
|
* @return A pair where the left value is the total count of way points and
|
||||||
* the right is the total list of way points that are >= minTime.
|
* the right is the total list of way points that are >= minTime.
|
||||||
*/
|
*/
|
||||||
private Pair<Integer, Integer> getCounts(List<MapWaypoint> points, Long minTime) {
|
private Pair<Integer, Integer> getCounts(List<Long> points, Long minTime) {
|
||||||
|
|
||||||
if (points == null) {
|
if (points == null) {
|
||||||
return EMPTY_COUNT;
|
return EMPTY_COUNT;
|
||||||
}
|
}
|
||||||
|
|
||||||
return points.stream().reduce(
|
return points.stream().reduce(
|
||||||
EMPTY_COUNT,
|
EMPTY_COUNT,
|
||||||
(total, w) -> Pair.of(total.getLeft() + 1, total.getRight() + (greaterThanOrEqual(minTime, w.getTimestamp()) ? 1 : 0)),
|
(total, time) -> Pair.of(total.getLeft() + 1, total.getRight() + (greaterThanOrEqual(minTime, time) ? 1 : 0)),
|
||||||
(pair1, pair2) -> Pair.of(pair1.getLeft() + pair2.getLeft(), pair1.getRight() + pair2.getRight()));
|
(pair1, pair2) -> Pair.of(pair1.getLeft() + pair2.getLeft(), pair1.getRight() + pair2.getRight()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
private static final long DAY_SECS = 24 * 60 * 60;
|
* Retrieves a tuple of the closest city (or null if a closest city cannot
|
||||||
|
* be determined) and the time stamp of the point in seconds from epoch. If
|
||||||
|
* the point is null, null is returned.
|
||||||
|
*
|
||||||
|
* @param cityMapper The means of mapping a point to the closest city.
|
||||||
|
* @param pt The geolocation point.
|
||||||
|
* @return A tuple of the closest city and timestamp in seconds from epoch.
|
||||||
|
*/
|
||||||
|
private Pair<CityRecord, Long> getClosestWithTime(ClosestCityMapper cityMapper, MapWaypoint pt) {
|
||||||
|
if (pt == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
CityRecord city = cityMapper.findClosest(new CityRecord(null, null, null, pt.getX(), pt.getY()));
|
||||||
|
|
||||||
|
Long time = pt.getTimestamp();
|
||||||
|
return Pair.of(city, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a set of waypoints representing a grouping (i.e. track, area)
|
||||||
|
* into a stream of the unique cities identified in this grouping and the
|
||||||
|
* latest time stamp for each grouping.
|
||||||
|
*
|
||||||
|
* @param points The points in the grouping.
|
||||||
|
* @param cityMapper The means of mapping a point to the closest city.
|
||||||
|
* @return A stream of tuples where each tuple will be a unique city (or
|
||||||
|
* null if a closest is not determined) and the latest timestamp for each.
|
||||||
|
*/
|
||||||
|
private Stream<Pair<CityRecord, Long>> reduceGrouping(Set<MapWaypoint> points, ClosestCityMapper cityMapper) {
|
||||||
|
if (points == null) {
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<CityRecord, Long> timeMapping = new HashMap<>();
|
||||||
|
for (MapWaypoint pt : points) {
|
||||||
|
Pair<CityRecord, Long> pair = getClosestWithTime(cityMapper, pt);
|
||||||
|
if (pair == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CityRecord city = pair.getLeft();
|
||||||
|
Long prevTime = timeMapping.get(city);
|
||||||
|
Long curTime = pair.getRight();
|
||||||
|
if (prevTime == null || (curTime != null && curTime > prevTime)) {
|
||||||
|
timeMapping.put(city, curTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return timeMapping.entrySet().stream()
|
||||||
|
.map(e -> Pair.of(e.getKey(), e.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a geo result taken from the Geolocation and convert to a stream
|
||||||
|
* of tuples where each tuple represents a point with the closest city and
|
||||||
|
* the time stamp in seconds from epoch.
|
||||||
|
*
|
||||||
|
* @param geoResult The result from the Geolocation API.
|
||||||
|
* @param cityMapper The means of mapping a point to the closest city.
|
||||||
|
* @return A list of tuples where each tuple represents a point to be
|
||||||
|
* counted with a combination of the closest city and the timestamp.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private Stream<Pair<CityRecord, Long>> processGeoResult(GeoResult geoResult, ClosestCityMapper cityMapper) {
|
||||||
|
if (geoResult == null) {
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Set<MapWaypoint>> areas = (geoResult.getAreas() == null) ? Collections.emptyList() : geoResult.getAreas();
|
||||||
|
List<Set<MapWaypoint>> tracks = (geoResult.getTracks() == null) ? Collections.emptyList() : geoResult.getTracks();
|
||||||
|
|
||||||
|
Stream<Pair<CityRecord, Long>> reducedGroupings = Stream.of(areas, tracks)
|
||||||
|
.flatMap((groupingList) -> groupingList.stream())
|
||||||
|
.flatMap((grouping) -> reduceGrouping(grouping, cityMapper));
|
||||||
|
|
||||||
|
final Set<MapWaypoint> allTracksAndAreas = Stream.of(areas, tracks)
|
||||||
|
.flatMap((groupingList) -> groupingList.stream())
|
||||||
|
.flatMap((group) -> group.stream())
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
Set<MapWaypoint> pointSet = geoResult.getMapWaypoints() == null ? Collections.emptySet() : geoResult.getMapWaypoints();
|
||||||
|
Stream<Pair<CityRecord, Long>> citiesForPoints = pointSet.stream()
|
||||||
|
// it appears that AbstractWaypointFetcher.handleFilteredWaypointSet returns all points
|
||||||
|
// (including track and area points) in the set of MapWaypoints. This filters those points out of the remaining.
|
||||||
|
.filter(pt -> !allTracksAndAreas.contains(pt))
|
||||||
|
.map(pt -> getClosestWithTime(cityMapper, pt));
|
||||||
|
|
||||||
|
return Stream.concat(reducedGroupings, citiesForPoints);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get this list of hits per city where the list is sorted descending by
|
* Get this list of hits per city where the list is sorted descending by
|
||||||
* number of found hits (i.e. most hits is first index).
|
* number of found hits (i.e. most hits is first index).
|
||||||
*
|
*
|
||||||
* @param dataSource The data source.
|
* @param dataSource The data source.
|
||||||
* @param daysCount Number of days to go back.
|
* @param daysCount Number of days to go back.
|
||||||
* @param maxCount Maximum number of results.
|
* @param maxCount Maximum number of results.
|
||||||
*
|
*
|
||||||
* @return The sorted list.
|
* @return The sorted list.
|
||||||
*
|
*
|
||||||
* @throws SleuthkitCaseProviderException
|
* @throws SleuthkitCaseProviderException
|
||||||
* @throws GeoLocationDataException
|
* @throws GeoLocationDataException
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
@ -300,31 +435,36 @@ public class GeolocationSummary implements DefaultArtifactUpdateGovernor {
|
|||||||
public CityData getCityCounts(DataSource dataSource, int daysCount, int maxCount)
|
public CityData getCityCounts(DataSource dataSource, int daysCount, int maxCount)
|
||||||
throws SleuthkitCaseProviderException, GeoLocationDataException, InterruptedException, IOException {
|
throws SleuthkitCaseProviderException, GeoLocationDataException, InterruptedException, IOException {
|
||||||
|
|
||||||
ClosestCityMapper closestCityMapper = ClosestCityMapper.getInstance();
|
ClosestCityMapper closestCityMapper = this.cityMapper.get();
|
||||||
|
GeoResult geoResult = getGeoResult(dataSource);
|
||||||
|
List<Pair<CityRecord, Long>> dataSourcePoints = processGeoResult(geoResult, closestCityMapper)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
List<MapWaypoint> dataSourcePoints = getPoints(dataSource);
|
Map<CityRecord, List<Long>> allCityPoints = new HashMap<>();
|
||||||
|
List<Long> others = new ArrayList<>();
|
||||||
Map<CityRecord, List<MapWaypoint>> allCityPoints = new HashMap<>();
|
|
||||||
List<MapWaypoint> others = new ArrayList<>();
|
|
||||||
Long mostRecent = null;
|
Long mostRecent = null;
|
||||||
|
|
||||||
for (MapWaypoint pt : dataSourcePoints) {
|
for (Pair<CityRecord, Long> pt : dataSourcePoints) {
|
||||||
CityRecord city = closestCityMapper.findClosest(new CityRecord(null, null, null, pt.getX(), pt.getY()));
|
if (pt == null) {
|
||||||
Long curTime = pt.getTimestamp();
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Long curTime = pt.getRight();
|
||||||
if (curTime != null && (mostRecent == null || curTime > mostRecent)) {
|
if (curTime != null && (mostRecent == null || curTime > mostRecent)) {
|
||||||
mostRecent = curTime;
|
mostRecent = curTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CityRecord city = pt.getLeft();
|
||||||
if (city == null) {
|
if (city == null) {
|
||||||
others.add(pt);
|
others.add(curTime);
|
||||||
} else {
|
} else {
|
||||||
List<MapWaypoint> cityPoints = allCityPoints.get(city);
|
List<Long> cityPoints = allCityPoints.get(city);
|
||||||
if (cityPoints == null) {
|
if (cityPoints == null) {
|
||||||
cityPoints = new ArrayList<>();
|
cityPoints = new ArrayList<>();
|
||||||
allCityPoints.put(city, cityPoints);
|
allCityPoints.put(city, cityPoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
cityPoints.add(pt);
|
cityPoints.add(curTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,7 +501,7 @@ public class GeolocationSummary implements DefaultArtifactUpdateGovernor {
|
|||||||
*/
|
*/
|
||||||
private static class PointFetcher extends AbstractWaypointFetcher {
|
private static class PointFetcher extends AbstractWaypointFetcher {
|
||||||
|
|
||||||
private final BlockingQueue<List<MapWaypoint>> asyncResult;
|
private final BlockingQueue<GeoResult> asyncResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main constructor.
|
* Main constructor.
|
||||||
@ -371,26 +511,16 @@ public class GeolocationSummary implements DefaultArtifactUpdateGovernor {
|
|||||||
* BlockingQueue blocks until a result is received from geolocation.
|
* BlockingQueue blocks until a result is received from geolocation.
|
||||||
* @param filters The applicable filters for geolocation.
|
* @param filters The applicable filters for geolocation.
|
||||||
*/
|
*/
|
||||||
public PointFetcher(BlockingQueue<List<MapWaypoint>> asyncResult, GeoFilter filters) {
|
public PointFetcher(BlockingQueue<GeoResult> asyncResult, GeoFilter filters) {
|
||||||
super(filters);
|
super(filters);
|
||||||
this.asyncResult = asyncResult;
|
this.asyncResult = asyncResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks, List<Set<MapWaypoint>> areas, boolean wasEntirelySuccessful) {
|
public void handleFilteredWaypointSet(Set<MapWaypoint> mapWaypoints, List<Set<MapWaypoint>> tracks, List<Set<MapWaypoint>> areas, boolean wasEntirelySuccessful) {
|
||||||
Stream<List<Set<MapWaypoint>>> stream = Stream.of(
|
|
||||||
Arrays.asList(mapWaypoints),
|
|
||||||
tracks == null ? Collections.emptyList() : tracks,
|
|
||||||
areas == null ? Collections.emptyList() : areas);
|
|
||||||
|
|
||||||
List<MapWaypoint> wayPoints = stream
|
|
||||||
.flatMap((List<Set<MapWaypoint>> list) -> list.stream())
|
|
||||||
.flatMap((Set<MapWaypoint> set) -> set.stream())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
// push to blocking queue to continue
|
// push to blocking queue to continue
|
||||||
try {
|
try {
|
||||||
asyncResult.put(wayPoints);
|
asyncResult.put(new GeoResult(mapWaypoints, tracks, areas));
|
||||||
} catch (InterruptedException ignored) {
|
} catch (InterruptedException ignored) {
|
||||||
// ignored cancellations
|
// ignored cancellations
|
||||||
}
|
}
|
||||||
@ -406,10 +536,12 @@ public class GeolocationSummary implements DefaultArtifactUpdateGovernor {
|
|||||||
* @throws GeoLocationDataException
|
* @throws GeoLocationDataException
|
||||||
* @throws InterruptedException
|
* @throws InterruptedException
|
||||||
*/
|
*/
|
||||||
private List<MapWaypoint> getPoints(DataSource dataSource) throws SleuthkitCaseProviderException, GeoLocationDataException, InterruptedException {
|
private GeoResult getGeoResult(DataSource dataSource)
|
||||||
|
throws SleuthkitCaseProviderException, GeoLocationDataException, InterruptedException {
|
||||||
|
|
||||||
// make asynchronous callback synchronous (the callback nature will be handled in a different level)
|
// make asynchronous callback synchronous (the callback nature will be handled in a different level)
|
||||||
// see the following: https://stackoverflow.com/questions/20659961/java-synchronous-callback
|
// see the following: https://stackoverflow.com/questions/20659961/java-synchronous-callback
|
||||||
final BlockingQueue<List<MapWaypoint>> asyncResult = new ArrayBlockingQueue<>(1);
|
final BlockingQueue<GeoResult> asyncResult = new ArrayBlockingQueue<>(1);
|
||||||
|
|
||||||
GeoFilter geoFilter = new GeoFilter(true, false, 0, Arrays.asList(dataSource), GPS_ARTIFACT_TYPES);
|
GeoFilter geoFilter = new GeoFilter(true, false, 0, Arrays.asList(dataSource), GPS_ARTIFACT_TYPES);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user