diff --git a/android/cpp-adapter.cpp b/android/cpp-adapter.cpp index b2344be..9a0064f 100644 --- a/android/cpp-adapter.cpp +++ b/android/cpp-adapter.cpp @@ -1,8 +1,10 @@ #include #include "react-native-clusterer.h" -extern "C" -JNIEXPORT void JNICALL -Java_com_reactnativeclusterer_ClustererModule_initialize(JNIEnv *env, jclass clazz, jlong jsi) { - clusterer::install(*reinterpret_cast(jsi)); +extern "C" JNIEXPORT void JNICALL +Java_com_reactnativeclusterer_ClustererModule_initialize(JNIEnv *env, + jclass clazz, + jlong jsi) { + clusterer::install(*reinterpret_cast(jsi)); + clusterer::installHelpers(*reinterpret_cast(jsi)); } \ No newline at end of file diff --git a/cpp/helpers.cpp b/cpp/helpers.cpp index 3a2e41d..2749747 100644 --- a/cpp/helpers.cpp +++ b/cpp/helpers.cpp @@ -221,4 +221,144 @@ void featurePropertyToJSI( return; } } + +double calculateDelta(double x, double y) { + if(x > y) { + return x - y; + } + return y - x; +} + +double calculateAverage(initializer_list args) { + if(args.size() == 0) { + return 0; + } + + double sum = 0; + for(auto &num : args) sum += num; + + return sum / args.size(); +} + +void installHelpers(jsi::Runtime &jsiRuntime) { + auto regionToBBox = jsi::Function::createFromHostFunction( + jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "regionToBBox"), 1, + [](jsi::Runtime &runtime, const jsi::Value &thisValue, + const jsi::Value *arguments, size_t count) -> jsi::Array { + jsi::Object region = arguments[0].getObject(runtime); + + double longitudeDelta = + region.getProperty(runtime, "longitudeDelta").asNumber(); + + double latitudeDelta = + region.getProperty(runtime, "latitudeDelta").asNumber(); + + double longitude = region.getProperty(runtime, "longitude").asNumber(); + + double latitude = region.getProperty(runtime, "latitude").asNumber(); + + double lngD = longitudeDelta; + + if(longitudeDelta < 0) { + lngD = longitudeDelta + 360; + } + + jsi::Array bbox = jsi::Array(runtime, 4); + + bbox.setValueAtIndex(runtime, 0, longitude - lngD); + bbox.setValueAtIndex(runtime, 1, latitude - latitudeDelta); + bbox.setValueAtIndex(runtime, 2, longitude + lngD); + bbox.setValueAtIndex(runtime, 3, latitude + latitudeDelta); + + return bbox; + }); + + auto getMarkersRegion = jsi::Function::createFromHostFunction( + jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "getMarkersRegion"), 1, + [](jsi::Runtime &runtime, const jsi::Value &thisValue, + const jsi::Value *arguments, size_t count) -> jsi::Object { + auto points = arguments[0].getObject(runtime).asArray(runtime); + + jsi::Object initialValue = + points.getValueAtIndex(runtime, 0).asObject(runtime); + + jsi::Object coordinates = jsi::Object(runtime); + + coordinates.setProperty( + runtime, "minX", + initialValue.getProperty(runtime, "latitude").asNumber()); + + coordinates.setProperty( + runtime, "maxX", + initialValue.getProperty(runtime, "latitude").asNumber()); + + coordinates.setProperty( + runtime, "minY", + initialValue.getProperty(runtime, "longitude").asNumber()); + + coordinates.setProperty( + runtime, "maxY", + initialValue.getProperty(runtime, "longitude").asNumber()); + + for(int i = 0; i < points.size(runtime); i++) { + jsi::Object point = + points.getValueAtIndex(runtime, i).asObject(runtime); + + double minX = + std::min(coordinates.getProperty(runtime, "minX").asNumber(), + point.getProperty(runtime, "latitude").asNumber()); + + double maxX = + std::max(coordinates.getProperty(runtime, "maxX").asNumber(), + point.getProperty(runtime, "latitude").asNumber()); + + double minY = + std::min(coordinates.getProperty(runtime, "minY").asNumber(), + point.getProperty(runtime, "longitude").asNumber()); + + double maxY = + std::max(coordinates.getProperty(runtime, "maxY").asNumber(), + point.getProperty(runtime, "longitude").asNumber()); + + coordinates.setProperty(runtime, "minX", minX); + + coordinates.setProperty(runtime, "maxX", maxX); + + coordinates.setProperty(runtime, "minY", minY); + + coordinates.setProperty(runtime, "maxY", maxY); + } + + double deltaX = + calculateDelta(coordinates.getProperty(runtime, "maxX").asNumber(), + coordinates.getProperty(runtime, "minX").asNumber()); + double deltaY = + calculateDelta(coordinates.getProperty(runtime, "maxY").asNumber(), + coordinates.getProperty(runtime, "minY").asNumber()); + + jsi::Object region = jsi::Object(runtime); + + region.setProperty( + runtime, "latitude", + calculateAverage( + {coordinates.getProperty(runtime, "minX").asNumber(), + coordinates.getProperty(runtime, "maxX").asNumber()})); + + region.setProperty( + runtime, "longitude", + calculateAverage( + {coordinates.getProperty(runtime, "minY").asNumber(), + coordinates.getProperty(runtime, "maxY").asNumber()})); + + region.setProperty(runtime, "latitudeDelta", deltaX * 1.5); + region.setProperty(runtime, "longitudeDelta", deltaY * 1.5); + + return region; + }); + + jsiRuntime.global().setProperty(jsiRuntime, "regionToBBox", + std::move(regionToBBox)); + jsiRuntime.global().setProperty(jsiRuntime, "getMarkersRegion", + std::move(getMarkersRegion)); +} } // namespace clusterer diff --git a/cpp/helpers.h b/cpp/helpers.h index 23a1e98..113e084 100644 --- a/cpp/helpers.h +++ b/cpp/helpers.h @@ -26,9 +26,11 @@ void tileToJSI(jsi::Runtime &rt, jsi::Object &jsiObject, mapbox::feature::feature &f, jsi::Array &featuresInput); -void featurePropertyToJSI(jsi::Runtime &rt, - jsi::Object &jsiFeatureProperties, - std::pair &itr, - int &origFeatureIndex); +void featurePropertyToJSI( + jsi::Runtime &rt, jsi::Object &jsiFeatureProperties, + std::pair &itr, + int &origFeatureIndex); + +void installHelpers(jsi::Runtime &jsiRuntime); } // namespace clusterer diff --git a/ios/Clusterer.mm b/ios/Clusterer.mm index 0f39920..5d67b12 100644 --- a/ios/Clusterer.mm +++ b/ios/Clusterer.mm @@ -41,4 +41,22 @@ + (BOOL)requiresMainQueueSetup { return @true; } +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(installHelpers) +{ + RCTBridge* bridge = [RCTBridge currentBridge]; + RCTCxxBridge* cxxBridge = (RCTCxxBridge*)bridge; + if (cxxBridge == nil) { + return @false; + } + + auto jsiRuntime = (jsi::Runtime*) cxxBridge.runtime; + if (jsiRuntime == nil) { + return @false; + } + + clusterer::installHelpers(*(facebook::jsi::Runtime *)jsiRuntime); + + return @true; +} + @end diff --git a/src/Supercluster.ts b/src/Supercluster.ts index dcfd809..93c23c7 100644 --- a/src/Supercluster.ts +++ b/src/Supercluster.ts @@ -1,9 +1,9 @@ import { NativeModules, Platform } from 'react-native'; import GeoViewport from '@mapbox/geo-viewport'; -import { getMarkersCoordinates, getMarkersRegion, regionToBBox } from './utils'; +import { getMarkersCoordinates } from './utils'; import type * as GeoJSON from 'geojson'; -import type { MapDimensions, Region } from './types'; +import type { MapDimensions, Region, BBox, LatLng } from './types'; import type Supercluster from './types'; const module = NativeModules.Clusterer; @@ -16,6 +16,19 @@ if ( module.install(); } +if ( + module && + typeof module.installHelpers === 'function' && + !(global as any).regionToBBox && + !(global as any).getMarkersRegion +) { + module.installHelpers(); +} + +const regionToBBox: (region: Region) => BBox = (global as any).regionToBBox; +const getMarkersRegion: (points: LatLng[]) => Region = (global as any) + .getMarkersRegion; + const createSupercluster = (global as any).createSupercluster; const defaultOptions = { minZoom: 0, // min zoom to generate clusters on diff --git a/src/utils.ts b/src/utils.ts index 57c3a3f..18b8cdb 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,60 +1,5 @@ import type { Feature, Point } from 'geojson'; import type Supercluster from './types'; -import type { BBox, LatLng, Region } from './types'; - -const calculateDelta = (x: number, y: number): number => - x > y ? x - y : y - x; - -const calculateAverage = (...args: number[]): number => { - const argList = [...args]; - if (!argList.length) { - return 0; - } - return argList.reduce((sum, num: number) => sum + num, 0) / argList.length; -}; - -export const regionToBBox = (region: Region): BBox => { - const lngD = - region.longitudeDelta < 0 - ? region.longitudeDelta + 360 - : region.longitudeDelta; - - return [ - region.longitude - lngD, // westLng - min lng - region.latitude - region.latitudeDelta, // southLat - min lat - region.longitude + lngD, // eastLng - max lng - region.latitude + region.latitudeDelta, // northLat - max lat - ]; -}; - -export const getMarkersRegion = (points: LatLng[]): Region => { - const coordinates = { - minX: points[0]!.latitude, - maxX: points[0]!.latitude, - maxY: points[0]!.longitude, - minY: points[0]!.longitude, - }; - - const { maxX, minX, maxY, minY } = points.reduce( - (acc, point) => ({ - minX: Math.min(acc.minX, point.latitude), - maxX: Math.max(acc.maxX, point.latitude), - minY: Math.min(acc.minY, point.longitude), - maxY: Math.max(acc.maxY, point.longitude), - }), - { ...coordinates } - ); - - const deltaX = calculateDelta(maxX, minX); - const deltaY = calculateDelta(maxY, minY); - - return { - latitude: calculateAverage(minX, maxX), - longitude: calculateAverage(minY, maxY), - latitudeDelta: deltaX * 1.5, - longitudeDelta: deltaY * 1.5, - }; -}; export const getMarkersCoordinates = (markers: Feature) => { const [longitude, latitude] = markers.geometry.coordinates;