diff --git a/database/getters.ts b/database/getters.ts index ec1641ba..dfb6a533 100644 --- a/database/getters.ts +++ b/database/getters.ts @@ -1,14 +1,16 @@ -import type { BoundingBox, Cluster, ComputedClusterSet, DatabaseArgs, DatabaseAuthArgs, DatabaseStatistics, Location, Suggestion } from '../types/index.ts' -import { Category, Currency, DbReadFunction, Provider } from '../types/index.ts' +import type { FeatureCollection } from '@turf/helpers' +import type { BoundingBox, ComputedClusterSet, DatabaseArgs, DatabaseAuthArgs, Location, Suggestion } from '../types/index.ts' +import { Category, Cryptocity, Currency, DbReadFunction, Provider } from '../types/index.ts' import { fetchDb } from './fetch.ts' /** - * We hardcode the Currencies, Categories and Providers here, because they are rarely updated. + * We hardcode these values here, because they are rarely updated. * Even if we update those values in the database, we always need to update UI regardless. */ export const CURRENCIES = Object.values(Currency) export const CATEGORIES = Object.values(Category) export const PROVIDERS = Object.values(Provider) +export const CRYPTOCITIES = Object.values(Cryptocity) // Maximum number of rows from the database const MAX_N_ROWS = 1000 @@ -27,7 +29,7 @@ export async function getLocations(dbArgs: DatabaseArgs | DatabaseAuthArgs, bbox return locations.map(parseLocations) } -export async function getLocation(dbArgs: DatabaseArgs, uuid: string, parseLocation: (l: Location) => Location): Promise { +export async function getLocation(dbArgs: DatabaseArgs | DatabaseAuthArgs, uuid: string, parseLocation: (l: Location) => Location): Promise { const params = new URLSearchParams() params.append('location_uuid', uuid) const location = await fetchDb(DbReadFunction.GetLocation, dbArgs, params.toString()) @@ -38,21 +40,19 @@ export async function getLocation(dbArgs: DatabaseArgs, uuid: string, parseLocat return parseLocation(location) } -export async function searchLocations(dbArgs: DatabaseArgs, query: string) { +export async function searchLocations(dbArgs: DatabaseArgs | DatabaseAuthArgs, query: string) { const params = new URLSearchParams() params.append('p_query', query) return await fetchDb[]>(DbReadFunction.SearchLocations, dbArgs, params.toString()) ?? [] } -type GetClustersReturn = ComputedClusterSet & { cryptocities: Cluster[] } -export async function getClusters(dbArgs: DatabaseArgs, bbox: BoundingBox, zoom: number, parseLocation: (l: Location) => Location = l => l): Promise { +export async function getClusters(dbArgs: DatabaseArgs | DatabaseAuthArgs, bbox: BoundingBox, zoom: number, parseLocation: (l: Location) => Location = l => l): Promise { const params = new URLSearchParams() Object.entries(bbox).forEach(([key, value]) => params.append(key.toLocaleLowerCase(), value.toString())) params.append('zoom_level', zoom.toString()) const res = await fetchDb(DbReadFunction.GetLocationsClustersSet, dbArgs, params.toString()) return { clusters: res?.clusters ?? [], - cryptocities: res?.cryptocities ?? [], singles: res?.singles.map(parseLocation) ?? [], } } @@ -61,10 +61,12 @@ export async function getClusters(dbArgs: DatabaseArgs, bbox: BoundingBox, zoom: * The maximum zoom level at which the clusters are computed in the database. * If the user zooms in more than this, the clusters will be computed in the client. */ -export async function getClusterMaxZoom(dbArgs: DatabaseArgs): Promise { +export async function getClusterMaxZoom(dbArgs: DatabaseArgs | DatabaseAuthArgs): Promise { return await fetchDb(DbReadFunction.GetMaxZoom, dbArgs) ?? -1 // FIXME: Show error to user instead of using -1 } -export async function getStats(dbArgs: DatabaseArgs): Promise { - return await fetchDb(DbReadFunction.GetStats, dbArgs) +export async function getCryptocityPolygon(dbArgs: DatabaseArgs | DatabaseAuthArgs, city: Cryptocity): Promise { + const params = new URLSearchParams() + params.append('city', city) + return await fetchDb(DbReadFunction.GetCryptocityPolygon, dbArgs, params.toString()) } diff --git a/package.json b/package.json index da2f2539..18126548 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@turf/boolean-point-in-polygon": "^6.5.0", "@turf/boolean-within": "^6.5.0", "@turf/helpers": "^6.5.0", + "@turf/intersect": "^6.5.0", "@turf/points-within-polygon": "^6.5.0", "@turf/union": "^6.5.0", "@types/google.maps": "^3.54.0", @@ -37,7 +38,7 @@ "@vueuse/math": "^10.4.1", "@vueuse/router": "^10.4.1", "pinia": "^2.1.6", - "radix-vue": "^0.1.33", + "radix-vue": "^0.2.3", "supercluster": "^8.0.1", "vue": "^3.3.4", "vue-i18n": "^9.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e4af8340..f202b77d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ dependencies: '@turf/helpers': specifier: ^6.5.0 version: 6.5.0 + '@turf/intersect': + specifier: ^6.5.0 + version: 6.5.0 '@turf/points-within-polygon': specifier: ^6.5.0 version: 6.5.0 @@ -60,8 +63,8 @@ dependencies: specifier: ^2.1.6 version: 2.1.6(typescript@5.1.6)(vue@3.3.4) radix-vue: - specifier: ^0.1.33 - version: 0.1.33(vue@3.3.4) + specifier: ^0.2.3 + version: 0.2.3(vue@3.3.4) supercluster: specifier: ^8.0.1 version: 8.0.1 @@ -763,6 +766,14 @@ packages: resolution: {integrity: sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==} dev: false + /@turf/intersect@6.5.0: + resolution: {integrity: sha512-2legGJeKrfFkzntcd4GouPugoqPUjexPZnOvfez+3SfIMrHvulw8qV8u7pfVyn2Yqs53yoVCEjS5sEpvQ5YRQg==} + dependencies: + '@turf/helpers': 6.5.0 + '@turf/invariant': 6.5.0 + polygon-clipping: 0.15.3 + dev: false + /@turf/invariant@6.5.0: resolution: {integrity: sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==} dependencies: @@ -4274,8 +4285,8 @@ packages: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true - /radix-vue@0.1.33(vue@3.3.4): - resolution: {integrity: sha512-2nwcjrXNXML//iOxUeqyh2bBXQtQt3Xy1NqM2oHZJTNNEaL2QnMjlSN51DXMzx3QB2uex2IwqA1cFTHbJ34tyw==} + /radix-vue@0.2.3(vue@3.3.4): + resolution: {integrity: sha512-kA+3XCO5zXStbTrzbibhyKh62TlL+5Nq5FMAfPjS5/Ni8Qf0FGfWb924O2q2sV85HfZKHa9XcACuMN1NKZz0KA==} dependencies: '@floating-ui/dom': 1.5.1 '@floating-ui/vue': 1.0.2(vue@3.3.4) diff --git a/shared/compute-cluster.ts b/shared/compute-cluster.ts index 9017a83d..4a1abf26 100644 --- a/shared/compute-cluster.ts +++ b/shared/compute-cluster.ts @@ -23,6 +23,7 @@ export function computeCluster(algorithm: Supercluster, locations: Location[], { lng: c.geometry.coordinates[0], lat: c.geometry.coordinates[1], count, + diameter: Math.max(24, Math.min(48, 0.24 * count + 24)), // Compute it lazily get expansionZoom() { return algorithm.getClusterExpansionZoom(clusterId) }, diff --git a/shared/geo-utils.ts b/shared/geo-utils.ts index beea2b69..16b3f2a5 100644 --- a/shared/geo-utils.ts +++ b/shared/geo-utils.ts @@ -3,6 +3,7 @@ import type { MultiPolygon } from '@turf/helpers' import { featureCollection, multiPolygon, point } from '@turf/helpers' import pointsWithinPolygon from '@turf/points-within-polygon' import union from '@turf/union' +import intersect from '@turf/intersect' import booleanWithin from '@turf/boolean-within' import type { BoundingBox, Point } from '../types/index.ts' @@ -70,3 +71,15 @@ export function addBBoxToArea(bbox: BoundingBox, multiPoly?: MultiPolygon) { export function getItemsWithinBBox(items: T[], bbox: BoundingBox) { return pointsWithinPolygon(featureCollection(items.map(toPoint)), toMultiPolygon(bbox)).features.flatMap(f => f.properties) } + +/** + * Checks if a bounding box is intersecting another bounding box. + * Since bounding boxes can cross the antimeridian, we need to check if any of the polygons created by toPolygon + * is within the othe multipolygon + */ +export function bBoxesIntersect(bbox1: BoundingBox, bbox2: BoundingBox) { + const [polygon1, polygon2] = [bbox1, bbox2].map(toPolygon) + return polygon1.some(p1 => polygon2.some(p2 => intersect(p1, p2))) +} + +export const distance = ({ lat: x1, lng: y1 }: Point, { lat: x2, lng: y2 }: Point) => Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) diff --git a/src/assets-dev/cryptocities-assets.ts b/src/assets-dev/cryptocities-assets.ts index 82bc87fb..1bf8d763 100644 --- a/src/assets-dev/cryptocities-assets.ts +++ b/src/assets-dev/cryptocities-assets.ts @@ -1,12 +1,13 @@ import { Cryptocity } from 'types' -import type { CryptocityContent } from 'types' +import type { CryptocityData } from 'types' import { i18n } from '@/i18n/i18n-setup' // Note that description is defined as a getter to be able to use the i18nKeyPassThrough, as the actual translation // for providerLabel is happening in i18n-t in MapMarkers -export const cryptocitiesContent: Record, CryptocityContent> = { +export const cryptocitiesData: Record = { [Cryptocity.SanJose]: { - name: `Criptociudad ${Cryptocity.SanJose}`, + cryptocity: Cryptocity.SanJose, + name: 'Criptociudad San José', get description() { return [ i18n.t('The San Jose Cryptocity Initiative is part of the Cryptocity endeavour led by Nimiq and its partners.'), @@ -15,5 +16,7 @@ export const cryptocitiesContent: Record, C ] }, url: 'https://www.criptociudad.cr/', + centroid: { lat: 9.935, lng: -84.102 }, + boundingBox: { neLat: 9.9720332, neLng: -84.0467977, swLat: 9.8998796, swLng: -84.1800327 }, }, } diff --git a/src/assets-dev/stories/locations.ts b/src/assets-dev/stories/locations.ts index 51f37a19..6d2ca9dc 100644 --- a/src/assets-dev/stories/locations.ts +++ b/src/assets-dev/stories/locations.ts @@ -1,9 +1,9 @@ import { CATEGORIES } from 'database' -import { Category, Cryptocity, Currency, type Location, LocationLink, Provider } from 'types' +import { Category, Currency, type Location, LocationLink, Provider } from 'types' import { providersAssets } from '../provider-assets' import { translateCategory } from '@/translations' -type ExtraFields = Pick +type ExtraFields = Pick export function getExtra(provider: Provider, sells: Currency[] = [], linkTo: LocationLink = LocationLink.GMaps): ExtraFields { const assets = providersAssets[provider] if (!assets) @@ -33,7 +33,6 @@ export function getExtra(provider: Provider, sells: Currency[] = [], linkTo: Loc sells, url, linkTo, - cryptocity: Cryptocity.None, } } diff --git a/src/components/cards/cryptocity/BasicInfoCryptocity.vue b/src/components/cards/cryptocity/BasicInfoCryptocity.vue index fcdb9353..c9d8ca7a 100644 --- a/src/components/cards/cryptocity/BasicInfoCryptocity.vue +++ b/src/components/cards/cryptocity/BasicInfoCryptocity.vue @@ -1,13 +1,12 @@ diff --git a/src/components/cards/cryptocity/CryptocityCard.vue b/src/components/cards/cryptocity/CryptocityCard.vue index e19762fd..e85e0f2d 100644 --- a/src/components/cards/cryptocity/CryptocityCard.vue +++ b/src/components/cards/cryptocity/CryptocityCard.vue @@ -1,18 +1,9 @@