Diversify scale fixture geography and privacy
This commit is contained in:
parent
4138d7bfef
commit
61ad7923ca
|
|
@ -1,7 +1,7 @@
|
|||
import maplibregl from "maplibre-gl"
|
||||
import {
|
||||
clusterSizeClass,
|
||||
mergeNearbyMapClusters
|
||||
mergeNearbyMapPoints
|
||||
} from "./map_cluster_points.mjs"
|
||||
|
||||
const trackingMinTimeMs = 5000
|
||||
|
|
@ -524,21 +524,19 @@ const createAidMap = element => {
|
|||
state.markerElements.clear()
|
||||
|
||||
const points = markerPoints(state.element)
|
||||
const exactPoints = points.filter(
|
||||
const displayPoints = mergeNearbyMapPoints(state.map, points)
|
||||
const exactPoints = displayPoints.filter(
|
||||
point => point.type !== "cluster" && (point.exact || !Number.isFinite(point.radius_meters))
|
||||
)
|
||||
const clusterPoints = mergeNearbyMapClusters(
|
||||
state.map,
|
||||
points.filter(point => point.type === "cluster")
|
||||
)
|
||||
const areaFeatures = areaFeatureCollection(points)
|
||||
const clusterPoints = displayPoints.filter(point => point.type === "cluster")
|
||||
const areaFeatures = areaFeatureCollection(displayPoints)
|
||||
const clusterFeatures = clusterFeatureCollection(clusterPoints)
|
||||
const bounds = new maplibregl.LngLatBounds()
|
||||
|
||||
exactPoints.forEach(point => bounds.extend([point.longitude, point.latitude]))
|
||||
areaFeatures.features.forEach(feature => extendBoundsWithFeature(bounds, feature))
|
||||
clusterFeatures.features.forEach(feature => bounds.extend(feature.geometry.coordinates))
|
||||
state.locationBounds = points.length > 0 ? bounds : null
|
||||
state.locationBounds = displayPoints.length > 0 ? bounds : null
|
||||
|
||||
if (state.map.isStyleLoaded()) {
|
||||
const areaSource = state.map.getSource(state.areaSourceId)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ const coordinatesFromPixel = (map, pixel) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const mergeNearbyMapClusters = (map, points, options = {}) => {
|
||||
const nearbyMapPointGroups = (map, points, options = {}) => {
|
||||
const gap = Number.isFinite(options.gap) ? Math.max(options.gap, 0) : 8
|
||||
|
||||
const groups = points.map(point => {
|
||||
|
|
@ -76,7 +76,10 @@ export const mergeNearbyMapClusters = (map, points, options = {}) => {
|
|||
}
|
||||
}
|
||||
|
||||
return groups.map(group => {
|
||||
return groups
|
||||
}
|
||||
|
||||
const visualCluster = (map, group) => {
|
||||
const coordinates = coordinatesFromPixel(map, group)
|
||||
const firstId = group.points[0]?.id || "anonymous"
|
||||
|
||||
|
|
@ -88,5 +91,17 @@ export const mergeNearbyMapClusters = (map, points, options = {}) => {
|
|||
longitude: coordinates.longitude,
|
||||
latitude: coordinates.latitude
|
||||
}
|
||||
}
|
||||
|
||||
export const mergeNearbyMapClusters = (map, points, options = {}) =>
|
||||
nearbyMapPointGroups(map, points, options).map(group => visualCluster(map, group))
|
||||
|
||||
export const mergeNearbyMapPoints = (map, points, options = {}) => {
|
||||
return nearbyMapPointGroups(map, points, options).map(group => {
|
||||
const point = group.points[0]
|
||||
|
||||
if (group.points.length === 1 && point?.type !== "cluster") return point
|
||||
|
||||
return visualCluster(map, group)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import test from "node:test"
|
|||
import {
|
||||
clusterDiameterPixels,
|
||||
clusterSizeClass,
|
||||
mergeNearbyMapClusters
|
||||
mergeNearbyMapClusters,
|
||||
mergeNearbyMapPoints
|
||||
} from "./map_cluster_points.mjs"
|
||||
|
||||
const flatMap = scale => ({
|
||||
|
|
@ -40,6 +41,36 @@ test("splits the same clusters again at a closer zoom", () => {
|
|||
assert.equal(mergeNearbyMapClusters(flatMap(3), points).length, 2)
|
||||
})
|
||||
|
||||
test("merges a singleton into an overlapping cluster at a distant zoom", () => {
|
||||
const points = [
|
||||
{id: "cluster", type: "cluster", count: 351, longitude: 0, latitude: 0},
|
||||
{id: "request", type: "request", longitude: 28, latitude: 0, exact: true}
|
||||
]
|
||||
|
||||
const result = mergeNearbyMapPoints(flatMap(1), points)
|
||||
|
||||
assert.equal(result.length, 1)
|
||||
assert.equal(result[0].type, "cluster")
|
||||
assert.equal(result[0].count, 352)
|
||||
})
|
||||
|
||||
test("keeps separated singleton points interactive at a closer zoom", () => {
|
||||
const points = [
|
||||
{id: "left", type: "request", longitude: 0, latitude: 0, exact: true},
|
||||
{
|
||||
id: "right",
|
||||
type: "request",
|
||||
longitude: 30,
|
||||
latitude: 0,
|
||||
radius_meters: 1_000
|
||||
}
|
||||
]
|
||||
|
||||
const result = mergeNearbyMapPoints(flatMap(3), points)
|
||||
|
||||
assert.deepEqual(result, points)
|
||||
})
|
||||
|
||||
test("uses larger non-overlapping markers for larger totals", () => {
|
||||
assert.equal(clusterDiameterPixels(999), 44)
|
||||
assert.equal(clusterDiameterPixels(1_000), 52)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -65,7 +65,21 @@ defmodule WhoNeedHelp.Activities do
|
|||
location_label: activity.location_label,
|
||||
public_location:
|
||||
fragment(
|
||||
"ST_SetSRID(ST_MakePoint(round(ST_X(?)::numeric, 2)::double precision, round(ST_Y(?)::numeric, 2)::double precision), 4326)",
|
||||
"""
|
||||
CASE
|
||||
WHEN ? @> '{"scale_fixture": true}'::jsonb THEN ?
|
||||
ELSE
|
||||
ST_SetSRID(
|
||||
ST_MakePoint(
|
||||
round(ST_X(?)::numeric, 2)::double precision,
|
||||
round(ST_Y(?)::numeric, 2)::double precision
|
||||
),
|
||||
4326
|
||||
)
|
||||
END
|
||||
""",
|
||||
activity.structured_data,
|
||||
activity.location,
|
||||
activity.location,
|
||||
activity.location
|
||||
)
|
||||
|
|
@ -849,16 +863,22 @@ defmodule WhoNeedHelp.Activities do
|
|||
fragment(
|
||||
"""
|
||||
ST_Intersects(
|
||||
ST_SetSRID(
|
||||
ST_MakePoint(
|
||||
round(ST_X(?)::numeric, 2)::double precision,
|
||||
round(ST_Y(?)::numeric, 2)::double precision
|
||||
),
|
||||
4326
|
||||
),
|
||||
CASE
|
||||
WHEN ? @> '{"scale_fixture": true}'::jsonb THEN ?
|
||||
ELSE
|
||||
ST_SetSRID(
|
||||
ST_MakePoint(
|
||||
round(ST_X(?)::numeric, 2)::double precision,
|
||||
round(ST_Y(?)::numeric, 2)::double precision
|
||||
),
|
||||
4326
|
||||
)
|
||||
END,
|
||||
ST_MakeEnvelope(?, ?, ?, ?, 4326)
|
||||
)
|
||||
""",
|
||||
activity.structured_data,
|
||||
activity.location,
|
||||
activity.location,
|
||||
activity.location,
|
||||
^west,
|
||||
|
|
|
|||
|
|
@ -80,7 +80,22 @@ defmodule WhoNeedHelp.Help do
|
|||
urgency: request.urgency,
|
||||
public_location:
|
||||
fragment(
|
||||
"CASE WHEN ? = 'exact_for_active_match' THEN ST_SetSRID(ST_MakePoint(round(ST_X(?)::numeric, 2)::double precision, round(ST_Y(?)::numeric, 2)::double precision), 4326) ELSE ? END",
|
||||
"""
|
||||
CASE
|
||||
WHEN ? @> '{"scale_fixture": true}'::jsonb THEN ?
|
||||
WHEN ? = 'exact_for_active_match' THEN
|
||||
ST_SetSRID(
|
||||
ST_MakePoint(
|
||||
round(ST_X(?)::numeric, 2)::double precision,
|
||||
round(ST_Y(?)::numeric, 2)::double precision
|
||||
),
|
||||
4326
|
||||
)
|
||||
ELSE ?
|
||||
END
|
||||
""",
|
||||
request.structured_data,
|
||||
request.location,
|
||||
request.location_visibility,
|
||||
request.location,
|
||||
request.location,
|
||||
|
|
@ -172,6 +187,7 @@ defmodule WhoNeedHelp.Help do
|
|||
AND
|
||||
ST_Intersects(
|
||||
CASE
|
||||
WHEN ? @> '{"scale_fixture": true}'::jsonb THEN ?
|
||||
WHEN ? = 'exact_for_active_match' THEN
|
||||
ST_SetSRID(
|
||||
ST_MakePoint(
|
||||
|
|
@ -191,6 +207,8 @@ defmodule WhoNeedHelp.Help do
|
|||
^(south - 0.005),
|
||||
^(east + 0.005),
|
||||
^(north + 0.005),
|
||||
request.structured_data,
|
||||
request.location,
|
||||
request.location_visibility,
|
||||
request.location,
|
||||
request.location,
|
||||
|
|
|
|||
|
|
@ -457,6 +457,42 @@ defmodule WhoNeedHelp.ActivitiesTest do
|
|||
end)
|
||||
end
|
||||
|
||||
test "scale fixture activities keep distinct synthetic public map coordinates", context do
|
||||
activities =
|
||||
for {latitude, index} <- [{"50.451", 1}, {"50.454", 2}] do
|
||||
{:ok, activity} =
|
||||
Activities.create_activity(
|
||||
context.organizer_scope,
|
||||
context.attrs
|
||||
|> Map.put("title", "Synthetic distinct activity #{index}")
|
||||
|> Map.put("latitude", latitude)
|
||||
)
|
||||
|
||||
activity
|
||||
|> Ecto.Changeset.change(
|
||||
structured_data: Map.put(activity.structured_data, "scale_fixture", true)
|
||||
)
|
||||
|> Repo.update!()
|
||||
end
|
||||
|
||||
items =
|
||||
Activities.map_discovery_items(
|
||||
context.outsider_scope,
|
||||
%{},
|
||||
viewport!(30.51, 50.44, 30.54, 50.46, 16)
|
||||
)
|
||||
|
||||
fixture_ids = MapSet.new(activities, & &1.id)
|
||||
|
||||
fixture_items =
|
||||
Enum.filter(items, fn
|
||||
%{type: "activity", id: id} -> MapSet.member?(fixture_ids, id)
|
||||
_item -> false
|
||||
end)
|
||||
|
||||
assert Enum.sort(Enum.map(fixture_items, & &1.latitude)) == [50.451, 50.454]
|
||||
end
|
||||
|
||||
test "hidden activity is discoverable only through its public area description", context do
|
||||
{:ok, hidden} =
|
||||
Activities.create_activity(
|
||||
|
|
|
|||
|
|
@ -603,6 +603,44 @@ defmodule WhoNeedHelp.MutualAidFlowTest do
|
|||
refute_in_delta item.longitude, 30.5267, 0.000_001
|
||||
end
|
||||
|
||||
test "scale fixture requests keep distinct synthetic public map coordinates", context do
|
||||
requests =
|
||||
for {latitude, index} <- [{"50.451", 1}, {"50.454", 2}] do
|
||||
{:ok, request} =
|
||||
Help.create_request(
|
||||
context.requester_scope,
|
||||
context.request_attrs
|
||||
|> Map.put("title", "Synthetic distinct request #{index}")
|
||||
|> Map.put("latitude", latitude)
|
||||
|> Map.put("location_visibility", "exact_for_active_match")
|
||||
)
|
||||
|
||||
request
|
||||
|> Ecto.Changeset.change(
|
||||
structured_data: Map.put(request.structured_data, "scale_fixture", true)
|
||||
)
|
||||
|> Repo.update!()
|
||||
end
|
||||
|
||||
items =
|
||||
Help.map_discovery_items(
|
||||
context.helper_scope,
|
||||
%{},
|
||||
viewport!(30.51, 50.44, 30.54, 50.46, 16)
|
||||
)
|
||||
|
||||
fixture_ids = MapSet.new(requests, & &1.id)
|
||||
|
||||
fixture_items =
|
||||
Enum.filter(items, fn
|
||||
%{type: "request", id: id} -> MapSet.member?(fixture_ids, id)
|
||||
_item -> false
|
||||
end)
|
||||
|
||||
assert Enum.sort(Enum.map(fixture_items, & &1.latitude)) == [50.451, 50.454]
|
||||
assert Enum.all?(fixture_items, &(&1.exact == false))
|
||||
end
|
||||
|
||||
test "area search treats SQL wildcard characters as literal text", context do
|
||||
{:ok, literal_request} =
|
||||
Help.create_request(
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user