mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-03-06 07:52:27 -05:00
Move to distance filtering to improve accuracy and speed
This commit is contained in:
@@ -22,6 +22,9 @@ export type profileQueryType = {
|
||||
is_smoker?: boolean | undefined,
|
||||
shortBio?: boolean | undefined,
|
||||
geodbCityIds?: String[] | undefined,
|
||||
lat?: number | undefined,
|
||||
lon?: number | undefined,
|
||||
radius?: number | undefined,
|
||||
compatibleWithUserId?: string | undefined,
|
||||
skipId?: string | undefined,
|
||||
orderBy?: string | undefined,
|
||||
@@ -49,12 +52,17 @@ export const loadProfiles = async (props: profileQueryType) => {
|
||||
is_smoker,
|
||||
shortBio,
|
||||
geodbCityIds,
|
||||
lat,
|
||||
lon,
|
||||
radius,
|
||||
compatibleWithUserId,
|
||||
orderBy: orderByParam = 'created_time',
|
||||
lastModificationWithin,
|
||||
skipId,
|
||||
} = props
|
||||
|
||||
const filterLocation = lat && lon && radius
|
||||
|
||||
const keywords = name ? name.split(",").map(q => q.trim()).filter(Boolean) : []
|
||||
// console.debug('keywords:', keywords)
|
||||
|
||||
@@ -90,6 +98,12 @@ export const loadProfiles = async (props: profileQueryType) => {
|
||||
(l.id.toString() != skipId) &&
|
||||
(!geodbCityIds ||
|
||||
(l.geodb_city_id && geodbCityIds.includes(l.geodb_city_id))) &&
|
||||
(!filterLocation ||(
|
||||
l.city_latitude && l.city_longitude &&
|
||||
Math.abs(l.city_latitude - lat) < radius / 69.0 &&
|
||||
Math.abs(l.city_longitude - lon) < radius / (69.0 * Math.cos(lat * Math.PI / 180)) &&
|
||||
Math.pow(l.city_latitude - lat, 2) + Math.pow((l.city_longitude - lon) * Math.cos(lat * Math.PI / 180), 2) < Math.pow(radius / 69.0, 2)
|
||||
)) &&
|
||||
((l.bio_length ?? 0) >= MIN_BIO_LENGTH)
|
||||
)
|
||||
|
||||
@@ -137,13 +151,13 @@ export const loadProfiles = async (props: profileQueryType) => {
|
||||
pref_relation_styles?.length &&
|
||||
where(
|
||||
`pref_relation_styles IS NULL OR pref_relation_styles = '{}' OR pref_relation_styles && $(pref_relation_styles)`,
|
||||
{ pref_relation_styles }
|
||||
{pref_relation_styles}
|
||||
),
|
||||
|
||||
pref_romantic_styles?.length &&
|
||||
where(
|
||||
`pref_romantic_styles IS NULL OR pref_romantic_styles = '{}' OR pref_romantic_styles && $(pref_romantic_styles)`,
|
||||
{ pref_romantic_styles }
|
||||
{pref_romantic_styles}
|
||||
),
|
||||
|
||||
!!wants_kids_strength &&
|
||||
@@ -163,6 +177,18 @@ export const loadProfiles = async (props: profileQueryType) => {
|
||||
geodbCityIds?.length &&
|
||||
where(`geodb_city_id = ANY($(geodbCityIds))`, {geodbCityIds}),
|
||||
|
||||
// miles par degree of lat: earth's radius (3950 miles) * pi / 180 = 69.0
|
||||
filterLocation && where(`
|
||||
city_latitude BETWEEN $(target_lat) - ($(radius) / 69.0)
|
||||
AND $(target_lat) + ($(radius) / 69.0)
|
||||
AND city_longitude BETWEEN $(target_lon) - ($(radius) / (69.0 * COS(RADIANS($(target_lat)))))
|
||||
AND $(target_lon) + ($(radius) / (69.0 * COS(RADIANS($(target_lat)))))
|
||||
AND SQRT(
|
||||
POWER(city_latitude - $(target_lat), 2)
|
||||
+ POWER((city_longitude - $(target_lon)) * COS(RADIANS($(target_lat))), 2)
|
||||
) <= $(radius) / 69.0
|
||||
`, {target_lat: lat, target_lon: lon, radius}),
|
||||
|
||||
skipId && where(`profiles.user_id != $(skipId)`, {skipId}),
|
||||
|
||||
orderBy(`${tablePrefix}.${orderByParam} DESC`),
|
||||
@@ -174,7 +200,7 @@ export const loadProfiles = async (props: profileQueryType) => {
|
||||
LEFT JOIN ${userActivityJoin}
|
||||
WHERE profiles.id = $(after)
|
||||
)`,
|
||||
{ after }
|
||||
{after}
|
||||
),
|
||||
|
||||
!shortBio && where(`bio_length >= ${MIN_BIO_LENGTH}`, {MIN_BIO_LENGTH}),
|
||||
|
||||
@@ -81,6 +81,15 @@ CREATE INDEX IF NOT EXISTS idx_profiles_last_mod_24h
|
||||
CREATE INDEX IF NOT EXISTS idx_profiles_bio_length
|
||||
ON profiles (bio_length);
|
||||
|
||||
-- Fastest general-purpose index
|
||||
DROP INDEX IF EXISTS profiles_lat_lon_idx;
|
||||
CREATE INDEX profiles_lat_lon_idx ON profiles (city_latitude, city_longitude);
|
||||
|
||||
-- Optional additional index for large tables / clustered inserts
|
||||
DROP INDEX IF EXISTS profiles_lat_lon_brin_idx;
|
||||
CREATE INDEX profiles_lat_lon_brin_idx ON profiles USING BRIN (city_latitude, city_longitude) WITH (pages_per_range = 32);
|
||||
|
||||
|
||||
|
||||
-- Functions and Triggers
|
||||
CREATE
|
||||
|
||||
@@ -355,6 +355,9 @@ export const API = (_apiTypeCheck = {
|
||||
is_smoker: z.coerce.boolean().optional(),
|
||||
shortBio: z.coerce.boolean().optional(),
|
||||
geodbCityIds: arraybeSchema.optional(),
|
||||
lat: z.coerce.number().optional(),
|
||||
lon: z.coerce.number().optional(),
|
||||
radius: z.coerce.number().optional(),
|
||||
compatibleWithUserId: z.string().optional(),
|
||||
orderBy: z
|
||||
.enum(['last_online_time', 'created_time', 'compatibility_score'])
|
||||
|
||||
@@ -2,9 +2,18 @@ import {Profile, ProfileRow} from "common/love/profile";
|
||||
import {cloneDeep} from "lodash";
|
||||
import {filterDefined} from "common/util/array";
|
||||
|
||||
// export type TargetArea = {
|
||||
// lat: number
|
||||
// lon: number
|
||||
// radius: number
|
||||
// }
|
||||
|
||||
export type FilterFields = {
|
||||
orderBy: 'last_online_time' | 'created_time' | 'compatibility_score'
|
||||
geodbCityIds: string[] | null
|
||||
lat: number | null
|
||||
lon: number | null
|
||||
radius: number | null
|
||||
genders: string[]
|
||||
name: string | undefined
|
||||
shortBio: boolean | undefined
|
||||
@@ -18,6 +27,7 @@ export type FilterFields = {
|
||||
| 'pref_age_min'
|
||||
| 'pref_age_max'
|
||||
>
|
||||
|
||||
export const orderProfiles = (
|
||||
profiles: Profile[],
|
||||
starredUserIds: string[] | undefined
|
||||
@@ -39,6 +49,9 @@ export const orderProfiles = (
|
||||
}
|
||||
export const initialFilters: Partial<FilterFields> = {
|
||||
geodbCityIds: undefined,
|
||||
lat: undefined,
|
||||
lon: undefined,
|
||||
radius: undefined,
|
||||
name: undefined,
|
||||
genders: undefined,
|
||||
pref_age_max: undefined,
|
||||
@@ -51,4 +64,8 @@ export const initialFilters: Partial<FilterFields> = {
|
||||
shortBio: undefined,
|
||||
orderBy: 'created_time',
|
||||
}
|
||||
export type OriginLocation = { id: string; name: string }
|
||||
|
||||
|
||||
export const FilterKeys = Object.keys(initialFilters) as (keyof FilterFields)[]
|
||||
|
||||
export type OriginLocation = { id: string; name: string, lat: number, lon: number }
|
||||
|
||||
@@ -25,6 +25,18 @@ export type locationType = {
|
||||
radius: number
|
||||
}
|
||||
|
||||
const skippedKeys = [
|
||||
'pref_age_min',
|
||||
'pref_age_max',
|
||||
'geodbCityIds',
|
||||
'orderBy',
|
||||
'shortBio',
|
||||
'targetArea',
|
||||
'lat',
|
||||
'lon',
|
||||
'radius',
|
||||
]
|
||||
|
||||
|
||||
export function formatFilters(filters: Partial<FilterFields>, location: locationType | null): String[] | null {
|
||||
const entries: String[] = []
|
||||
@@ -53,7 +65,7 @@ export function formatFilters(filters: Partial<FilterFields>, location: location
|
||||
const typedKey = key as keyof FilterFields
|
||||
|
||||
if (value === undefined || value === null) return
|
||||
if (typedKey == 'pref_age_min' || typedKey == 'pref_age_max' || typedKey == 'geodbCityIds' || typedKey == 'orderBy' || typedKey == 'shortBio') return
|
||||
if (skippedKeys.includes(typedKey)) return
|
||||
if (Array.isArray(value) && value.length === 0) return
|
||||
if (initialFilters[typedKey] === value) return
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ export function LocationFilter(props: {
|
||||
if (!city) {
|
||||
setLocation(undefined)
|
||||
} else {
|
||||
setLocation({ id: city.geodb_city_id, name: city.city })
|
||||
setLocation({ id: city.geodb_city_id, name: city.city, lat: city.latitude, lon: city.longitude })
|
||||
setLastCity(city)
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ function DistanceSlider(props: {
|
||||
}) {
|
||||
const { radius, setRadius } = props
|
||||
|
||||
const snapValues = [10, 50, 100, 200, 300]
|
||||
const snapValues = [10, 50, 100, 200, 300, 500]
|
||||
|
||||
const snapToValue = (value: number) => {
|
||||
const closest = snapValues.reduce((prev, curr) =>
|
||||
@@ -158,7 +158,7 @@ function LocationResults(props: {
|
||||
}) {
|
||||
const { showAny, cities, onCitySelected, loading, className } = props
|
||||
|
||||
// delay loading animation by 150ms
|
||||
// delay loading animation by 150 ms
|
||||
const [debouncedLoading, setDebouncedLoading] = useState(loading)
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import {Profile} from "common/love/profile";
|
||||
import {useIsLooking} from "web/hooks/use-is-looking";
|
||||
import {usePersistentLocalState} from "web/hooks/use-persistent-local-state";
|
||||
import {useCallback} from "react";
|
||||
import {useCallback, useEffect} from "react";
|
||||
import {debounce, isEqual} from "lodash";
|
||||
import {useNearbyCities} from "web/hooks/use-nearby-locations";
|
||||
import {useEffectCheckEquality} from "web/hooks/use-effect-check-equality";
|
||||
import {wantsKidsDatabase, wantsKidsDatabaseToWantsKidsFilter, wantsKidsToHasKidsFilter} from "common/wants-kids";
|
||||
import {FilterFields, initialFilters, OriginLocation} from "common/filters";
|
||||
import {MAX_INT, MIN_INT} from "common/constants";
|
||||
@@ -13,9 +11,11 @@ export const useFilters = (you: Profile | undefined) => {
|
||||
const isLooking = useIsLooking()
|
||||
const [filters, setFilters] = usePersistentLocalState<Partial<FilterFields>>(
|
||||
isLooking ? initialFilters : {...initialFilters, orderBy: 'created_time'},
|
||||
'profile-filters-2'
|
||||
'profile-filters-4'
|
||||
)
|
||||
|
||||
// console.log('filters', filters)
|
||||
|
||||
const updateFilter = (newState: Partial<FilterFields>) => {
|
||||
const updatedState = {...newState}
|
||||
|
||||
@@ -31,6 +31,8 @@ export const useFilters = (you: Profile | undefined) => {
|
||||
}
|
||||
}
|
||||
|
||||
// console.log('updating filters', updatedState)
|
||||
|
||||
setFilters((prevState) => ({...prevState, ...updatedState}))
|
||||
}
|
||||
|
||||
@@ -54,11 +56,17 @@ export const useFilters = (you: Profile | undefined) => {
|
||||
OriginLocation | undefined | null
|
||||
>(undefined, 'nearby-origin-location')
|
||||
|
||||
const nearbyCities = useNearbyCities(location?.id, radius)
|
||||
// const nearbyCities = useNearbyCities(location?.id, radius)
|
||||
//
|
||||
// useEffectCheckEquality(() => {
|
||||
// updateFilter({geodbCityIds: nearbyCities})
|
||||
// }, [nearbyCities])
|
||||
|
||||
useEffectCheckEquality(() => {
|
||||
updateFilter({geodbCityIds: nearbyCities})
|
||||
}, [nearbyCities])
|
||||
useEffect(() => {
|
||||
if (location?.lat && location?.lon) {
|
||||
updateFilter({lat: location.lat, lon: location.lon, radius: radius})
|
||||
}
|
||||
}, [location?.id, radius]);
|
||||
|
||||
const locationFilterProps = {
|
||||
location,
|
||||
@@ -99,8 +107,8 @@ export const useFilters = (you: Profile | undefined) => {
|
||||
updateFilter(yourFilters)
|
||||
setRadius(100)
|
||||
debouncedSetRadius(100) // clear any pending debounced sets
|
||||
if (you?.geodb_city_id && you.city) {
|
||||
setLocation({id: you?.geodb_city_id, name: you?.city})
|
||||
if (you?.geodb_city_id && you.city && you.city_latitude && you.city_longitude) {
|
||||
setLocation({id: you?.geodb_city_id, name: you?.city, lat: you?.city_latitude, lon: you?.city_longitude})
|
||||
}
|
||||
} else {
|
||||
clearFilters()
|
||||
|
||||
@@ -6,7 +6,7 @@ import {useCompatibleProfiles} from 'web/hooks/use-profiles'
|
||||
import {getStars} from 'web/lib/supabase/stars'
|
||||
import {useCallback, useEffect, useRef, useState} from 'react'
|
||||
import {ProfileGrid} from 'web/components/profile-grid'
|
||||
import {CompassLoadingIndicator, LoadingIndicator} from 'web/components/widgets/loading-indicator'
|
||||
import {CompassLoadingIndicator} from 'web/components/widgets/loading-indicator'
|
||||
import {Title} from 'web/components/widgets/title'
|
||||
import {useGetter} from 'web/hooks/use-getter'
|
||||
import {usePersistentInMemoryState} from 'web/hooks/use-persistent-in-memory-state'
|
||||
@@ -28,7 +28,7 @@ export function ProfilesHome() {
|
||||
locationFilterProps,
|
||||
} = useFilters(you ?? undefined);
|
||||
|
||||
const [profiles, setProfiles] = usePersistentInMemoryState<Profile[] | undefined>(undefined, 'profile-profiles');
|
||||
const [profiles, setProfiles] = usePersistentInMemoryState<Profile[] | undefined>(undefined, 'profiles');
|
||||
const {bookmarkedSearches, refreshBookmarkedSearches} = useBookmarkedSearches(user?.id)
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||
const [isReloading, setIsReloading] = useState(false);
|
||||
|
||||
Reference in New Issue
Block a user