├── .npmignore ├── .gitattributes ├── ios ├── YamapView.h ├── YamapCircle.h ├── YamapMarker.h ├── YamapPolygon.h ├── YamapPolyline.h ├── ClusteredYamapView.h ├── RNYamap.h ├── RNYamap-Bridging-Header.h ├── View │ ├── RNCYMView.h │ ├── YamapCircleView.h │ ├── YamapPolygonView.h │ ├── YamapPolylineView.h │ ├── YamapMarkerView.h │ ├── RNYMView.h │ ├── YamapCircleView.m │ ├── YamapPolylineView.m │ ├── YamapPolygonView.m │ ├── YamapMarkerView.m │ └── RNCYMView.m ├── YamapSuggests.m ├── YamapSearch.m ├── YamapCircle.m ├── RNYamap.m ├── YamapPolygon.m ├── Converter │ └── RCTConvert+Yamap.m ├── YamapPolyline.m ├── YamapMarker.m ├── YamapSuggests.swift ├── ClusteredYamapView.m ├── YamapSearch.swift └── YamapView.m ├── android ├── src │ └── main │ │ ├── java │ │ └── ru │ │ │ └── vvdev │ │ │ └── yamap │ │ │ ├── utils │ │ │ ├── Callback.kt │ │ │ ├── RouteManager.kt │ │ │ └── ImageLoader.kt │ │ │ ├── models │ │ │ └── ReactMapObject.kt │ │ │ ├── suggest │ │ │ ├── MapSuggestItem.kt │ │ │ ├── RNYandexSuggestPackage.kt │ │ │ ├── MapSuggestClient.kt │ │ │ ├── YandexSuggestRNArgsHelper.kt │ │ │ ├── RNYandexSuggestModule.kt │ │ │ └── YandexMapSuggestClient.kt │ │ │ ├── search │ │ │ ├── MapSearchItem.kt │ │ │ ├── MapSearchClient.kt │ │ │ ├── YandexSearchRNArgsHelper.kt │ │ │ └── YandexMapSearchClient.kt │ │ │ ├── RNYamapPackage.kt │ │ │ ├── YamapCircleManager.kt │ │ │ ├── RNYamapModule.kt │ │ │ ├── view │ │ │ ├── YamapCircle.kt │ │ │ ├── YamapPolygon.kt │ │ │ ├── YamapPolyline.kt │ │ │ ├── ClusteredYamapView.kt │ │ │ └── YamapMarker.kt │ │ │ ├── YamapPolylineManager.kt │ │ │ ├── YamapPolygonManager.kt │ │ │ └── YamapMarkerManager.kt │ │ └── AndroidManifest.xml └── build.gradle ├── src ├── geocoding │ ├── GeocodingApiError.ts │ ├── Query.ts │ └── index.ts ├── index.ts ├── utils │ ├── index.ts │ └── CallbacksManager.ts ├── components │ ├── Circle.tsx │ ├── Polygon.tsx │ ├── Polyline.tsx │ ├── Marker.tsx │ ├── Yamap.tsx │ └── ClusteredYamap.tsx ├── interfaces.ts ├── Suggest.ts └── Search.ts ├── tsconfig.json ├── .gitignore ├── RNYamap.podspec └── package.json /.npmignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text -------------------------------------------------------------------------------- /ios/YamapView.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface YamapView : RCTViewManager 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/utils/Callback.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.utils 2 | 3 | interface Callback { 4 | fun invoke(arg: T) 5 | } 6 | -------------------------------------------------------------------------------- /ios/YamapCircle.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface YamapCircle : RCTViewManager 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/YamapMarker.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface YamapMarker : RCTViewManager 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/YamapPolygon.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface YamapPolygon : RCTViewManager 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/YamapPolyline.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface YamapPolyline : RCTViewManager 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/ClusteredYamapView.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface ClusteredYamapView : RCTViewManager 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/models/ReactMapObject.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.models 2 | 3 | import com.yandex.mapkit.map.MapObject 4 | 5 | interface ReactMapObject { 6 | var rnMapObject: MapObject? 7 | } 8 | -------------------------------------------------------------------------------- /src/geocoding/GeocodingApiError.ts: -------------------------------------------------------------------------------- 1 | export class GeocodingApiError extends Error { 2 | readonly yandexResponse: any; 3 | 4 | constructor(response: any) { 5 | super('api error'); 6 | this.yandexResponse = response; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ios/RNYamap.h: -------------------------------------------------------------------------------- 1 | #if __has_include("RCTBridgeModule.h") 2 | #import "RCTBridgeModule.h" 3 | #else 4 | #import 5 | #endif 6 | #import "YamapView.h" 7 | 8 | @interface yamap : NSObject 9 | 10 | @property YamapView *map; 11 | 12 | - (void)initWithKey:(NSString*)apiKey; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/suggest/MapSuggestItem.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.suggest 2 | 3 | class MapSuggestItem { 4 | @JvmField 5 | var searchText: String? = null 6 | @JvmField 7 | var title: String? = null 8 | @JvmField 9 | var subtitle: String? = null 10 | @JvmField 11 | var uri: String? = null 12 | } 13 | -------------------------------------------------------------------------------- /ios/RNYamap-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // newYamap-Bridging-Header.h 3 | // newYamap 4 | // 5 | // Created by Tim on 06.08.24. 6 | // 7 | 8 | #import "React/RCTBridgeModule.h" 9 | #import "React/RCTEventEmitter.h" 10 | #import 11 | #import 12 | #import 13 | #import 14 | #import 15 | #import 16 | #import 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "jsx": "react", 10 | "lib": ["dom", "es5", "es6", "scripthost"], 11 | "target": "es5", 12 | "rootDir": "./src", 13 | "outDir": "build", 14 | "sourceMap": true, 15 | "skipLibCheck": true 16 | }, 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components/Yamap'; 2 | export * from './components/Marker'; 3 | export * from './components/Polygon'; 4 | export * from './components/Polyline'; 5 | export * from './components/Circle'; 6 | export * from './components/ClusteredYamap'; 7 | export * from './geocoding'; 8 | export * from './interfaces'; 9 | export * from './Suggest'; 10 | 11 | export { default as Suggest } from './Suggest'; 12 | export { default as Search } from './Search'; 13 | export { YaMap as default } from './components/Yamap'; 14 | -------------------------------------------------------------------------------- /ios/View/RNCYMView.h: -------------------------------------------------------------------------------- 1 | #ifndef RNCYMView_h 2 | #define RNCYMView_h 3 | #import 4 | 5 | #import 6 | #import 7 | @import YandexMapsMobile; 8 | 9 | @class RCTBridge; 10 | 11 | @interface RNCYMView: RNYMView 12 | 13 | - (void)setClusterColor:(UIColor*_Nullable)color; 14 | - (void)setClusteredMarkers:(NSArray*_Nonnull)points; 15 | - (void)setInitialRegion:(NSDictionary *_Nullable)initialRegion; 16 | 17 | @end 18 | 19 | #endif /* RNYMView_h */ 20 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { processColor } from 'react-native'; 2 | 3 | export function processColorProps(props: T, name: keyof T) { 4 | if (props[name]) { 5 | /* eslint-disable no-param-reassign */ 6 | // @ts-ignore 7 | props[name] = processColor(props[name]); 8 | /* eslint-enable no-param-reassign */ 9 | } 10 | } 11 | 12 | export function guid() { 13 | function s4() { 14 | return Math.floor((1 + Math.random()) * 0x10000) 15 | .toString(16) 16 | .substring(1); 17 | } 18 | return `${s4()}${s4()}-${s4()}-${s4()}-${s4()}-${s4()}${s4()}${s4()}`; 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # OSX 3 | # 4 | .DS_Store 5 | 6 | # node.js 7 | # 8 | node_modules/ 9 | npm-debug.log 10 | yarn-error.log 11 | 12 | 13 | # Xcode 14 | # 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | *.xccheckout 25 | *.moved-aside 26 | DerivedData 27 | *.hmap 28 | *.ipa 29 | *.xcuserstate 30 | project.xcworkspace 31 | 32 | 33 | # Android/IntelliJ 34 | # 35 | .idea 36 | .gradle 37 | local.properties 38 | *.iml 39 | 40 | # BUCK 41 | buck-out/ 42 | \.buckd/ 43 | *.keystore 44 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/search/MapSearchItem.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.search 2 | 3 | import com.yandex.mapkit.geometry.Point 4 | 5 | class MapSearchItemComponent { 6 | @JvmField 7 | var kind: String? = null 8 | @JvmField 9 | var name: String? = null 10 | } 11 | 12 | class MapSearchItem { 13 | @JvmField 14 | var formatted: String? = null 15 | @JvmField 16 | var country_code: String? = null 17 | @JvmField 18 | var uri: String? = null 19 | @JvmField 20 | var point: Point? = null 21 | @JvmField 22 | var Components: ArrayList? = null 23 | } -------------------------------------------------------------------------------- /ios/YamapSuggests.m: -------------------------------------------------------------------------------- 1 | #import "React/RCTBridgeModule.h" 2 | #import "React/RCTEventEmitter.h" 3 | 4 | @interface RCT_EXTERN_MODULE(YamapSuggests, RCTEventEmitter) 5 | 6 | RCT_EXTERN_METHOD(suggest:(nonnull NSString*) searchQuery resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject) 7 | 8 | RCT_EXTERN_METHOD(suggestWithOptions:(nonnull NSString*) searchQuery options:(NSDictionary *) options resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject) 9 | 10 | RCT_EXTERN_METHOD(reset: (RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject) 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /src/utils/CallbacksManager.ts: -------------------------------------------------------------------------------- 1 | import { guid } from './index'; 2 | 3 | export default class CallbacksManager { 4 | static callbacks: { [id: string]: (...arg: any[]) => void} = {}; 5 | 6 | static addCallback(callback: (...arg: any[]) => void) { 7 | const id = guid(); 8 | CallbacksManager.callbacks[id] = (...args) => { 9 | callback(...args); 10 | delete CallbacksManager.callbacks[id]; 11 | }; 12 | return id; 13 | } 14 | 15 | static call(id: string, ...args: any[]) { 16 | if (CallbacksManager.callbacks[id]) { 17 | CallbacksManager.callbacks[id](...args); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/geocoding/Query.ts: -------------------------------------------------------------------------------- 1 | interface InData { 2 | [key: string]: string | number | boolean | undefined | null; 3 | } 4 | 5 | interface ParsedData { 6 | [key: string]: string | number | boolean; 7 | } 8 | 9 | export default class Query { 10 | private _data: ParsedData; 11 | 12 | constructor(data: InData) { 13 | this._data = JSON.parse(JSON.stringify(data)); 14 | } 15 | 16 | public toQueryString(): string { 17 | let res = ''; 18 | 19 | for (const key in this._data) { 20 | const AMPERSAND = res.length > 0 ? '&' : ''; 21 | res = `${res}${AMPERSAND}${encodeURIComponent(key)}=${encodeURIComponent(this._data[key])}`; 22 | } 23 | 24 | return res; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /RNYamap.podspec: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | Pod::Spec.new do |s| 4 | package = JSON.parse(File.read(File.join(File.dirname(__FILE__), "package.json"))) 5 | s.name = "RNYamap" 6 | s.version = package["version"] 7 | s.summary = package["description"] 8 | s.homepage = "vvdev.ru" 9 | s.license = "MIT" 10 | s.author = { package["author"]["name"] => package["author"]["email"] } 11 | s.platform = :ios, "12.0" 12 | s.source = { :git => "https://github.com/author/RNYamap.git", :tag => "master" } 13 | s.source_files = "ios/**/*.{h,m,swift}" 14 | # s.requires_arc = true 15 | 16 | s.dependency "React" 17 | s.dependency "YandexMapsMobile", "4.19.0-full" 18 | end 19 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/suggest/RNYandexSuggestPackage.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.suggest 2 | 3 | import com.facebook.react.ReactPackage 4 | import com.facebook.react.bridge.NativeModule 5 | import com.facebook.react.bridge.ReactApplicationContext 6 | import com.facebook.react.uimanager.ViewManager 7 | import java.util.Arrays 8 | 9 | class RNYandexSuggestPackage : ReactPackage { 10 | override fun createNativeModules(reactContext: ReactApplicationContext): List { 11 | return Arrays.asList(RNYandexSuggestModule(reactContext)) 12 | } 13 | 14 | override fun createViewManagers(reactContext: ReactApplicationContext): List> { 15 | return emptyList() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ios/View/YamapCircleView.h: -------------------------------------------------------------------------------- 1 | #ifndef YamapCircleView_h 2 | #define YamapCircleView_h 3 | #import 4 | @import YandexMapsMobile; 5 | 6 | @interface YamapCircleView: UIView 7 | 8 | @property (nonatomic, copy) RCTBubblingEventBlock onPress; 9 | 10 | // PROPS 11 | - (void)setFillColor:(UIColor*)color; 12 | - (void)setStrokeColor:(UIColor*)color; 13 | - (void)setStrokeWidth:(NSNumber*)width; 14 | - (void)setZIndex:(NSNumber*)_zIndex; 15 | - (void)setCircleCenter:(YMKPoint*)center; 16 | - (void)setRadius:(float)radius; 17 | - (YMKCircle*)getCircle; 18 | - (YMKPolygonMapObject*)getMapObject; 19 | - (void)setMapObject:(YMKCircleMapObject*)mapObject; 20 | - (void)setHandled:(BOOL)_handled; 21 | 22 | @end 23 | 24 | #endif /* YamapCircleView */ 25 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/suggest/MapSuggestClient.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.suggest 2 | 3 | import com.facebook.react.bridge.ReadableMap 4 | import ru.vvdev.yamap.utils.Callback 5 | 6 | interface MapSuggestClient { 7 | /** 8 | * Получить саджесты по тексту `text`. 9 | * Вернуть результат в метод `onSuccess` в случае успеха, в случае неудачи в `onError` 10 | */ 11 | fun suggest( 12 | text: String?, 13 | onSuccess: Callback?>?, 14 | onError: Callback? 15 | ) 16 | 17 | fun suggest( 18 | text: String?, 19 | options: ReadableMap?, 20 | onSuccess: Callback?>?, 21 | onError: Callback? 22 | ) 23 | /** 24 | * Остановить сессию поиска саджестов 25 | */ 26 | fun resetSuggest() 27 | } 28 | -------------------------------------------------------------------------------- /ios/View/YamapPolygonView.h: -------------------------------------------------------------------------------- 1 | #ifndef YamapPolygonView_h 2 | #define YamapPolygonView_h 3 | #import 4 | @import YandexMapsMobile; 5 | 6 | @interface YamapPolygonView: UIView 7 | 8 | @property (nonatomic, copy) RCTBubblingEventBlock onPress; 9 | 10 | // PROPS 11 | - (void)setFillColor:(UIColor*)color; 12 | - (void)setStrokeColor:(UIColor*)color; 13 | - (void)setStrokeWidth:(NSNumber*)width; 14 | - (void)setZIndex:(NSNumber*)_zIndex; 15 | - (void)setPolygonPoints:(NSArray*)_points; 16 | - (void)setInnerRings:(NSArray*>*)_innerRings; 17 | - (NSMutableArray*)getPoints; 18 | - (YMKPolygon*)getPolygon; 19 | - (YMKPolygonMapObject*)getMapObject; 20 | - (void)setMapObject:(YMKPolygonMapObject*)mapObject; 21 | - (void)setHandled:(BOOL)_handled; 22 | 23 | @end 24 | 25 | #endif /* YamapPoligonView */ 26 | -------------------------------------------------------------------------------- /src/components/Circle.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { requireNativeComponent } from 'react-native'; 3 | import { processColorProps } from '../utils'; 4 | import { Point } from '../interfaces'; 5 | 6 | export interface CircleProps { 7 | fillColor?: string; 8 | strokeColor?: string; 9 | strokeWidth?: number; 10 | zIndex?: number; 11 | onPress?: () => void; 12 | center: Point; 13 | radius: number; 14 | children?: undefined; 15 | handled?: boolean; 16 | } 17 | 18 | const NativeCircleComponent = requireNativeComponent('YamapCircle'); 19 | 20 | export class Circle extends React.Component { 21 | static defaultProps = { 22 | }; 23 | 24 | render() { 25 | const props = { ...this.props }; 26 | 27 | processColorProps(props, 'fillColor' as keyof CircleProps); 28 | processColorProps(props, 'strokeColor' as keyof CircleProps); 29 | 30 | return ; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/suggest/YandexSuggestRNArgsHelper.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.suggest 2 | 3 | import com.facebook.react.bridge.Arguments 4 | import com.facebook.react.bridge.WritableArray 5 | import com.facebook.react.bridge.WritableMap 6 | 7 | class YandexSuggestRNArgsHelper { 8 | fun createSuggestsMapFrom(data: List?): WritableArray { 9 | val result = Arguments.createArray() 10 | 11 | if (data != null) { 12 | for (i in data.indices) { 13 | result.pushMap(data[i]?.let { createSuggestMapFrom(it) }) 14 | } 15 | } 16 | 17 | return result 18 | } 19 | 20 | private fun createSuggestMapFrom(data: MapSuggestItem): WritableMap { 21 | val result = Arguments.createMap() 22 | result.putString("title", data.title) 23 | result.putString("subtitle", data.subtitle) 24 | result.putString("uri", data.uri) 25 | 26 | return result 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/utils/RouteManager.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.utils 2 | 3 | import com.yandex.mapkit.map.MapObject 4 | import com.yandex.mapkit.transport.masstransit.Route 5 | import java.util.UUID 6 | 7 | // todo: не используется 8 | class RouteManager { 9 | private val data = HashMap() 10 | private val mapObjects = HashMap>() 11 | 12 | fun saveRoute(route: Route, id: String) { 13 | data[id] = route 14 | } 15 | 16 | fun putRouteMapObjects(id: String, objects: ArrayList) { 17 | mapObjects[id] = objects 18 | } 19 | 20 | fun getRoute(id: String): Route? { 21 | return data[id] 22 | } 23 | 24 | fun getRouteMapObjects(id: String): ArrayList { 25 | return mapObjects[id]!! 26 | } 27 | 28 | companion object { 29 | fun generateId(): String { 30 | return UUID.randomUUID().toString() 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ios/View/YamapPolylineView.h: -------------------------------------------------------------------------------- 1 | #ifndef YamapPolylineView_h 2 | #define YamapPolylineView_h 3 | #import 4 | @import YandexMapsMobile; 5 | 6 | @interface YamapPolylineView: UIView 7 | 8 | @property (nonatomic, copy) RCTBubblingEventBlock onPress; 9 | 10 | // PROPS 11 | - (void)setOutlineColor:(UIColor*)color; 12 | - (void)setStrokeColor:(UIColor*)color; 13 | - (void)setStrokeWidth:(NSNumber*)width; 14 | - (void)setDashLength:(NSNumber*)length; 15 | - (void)setGapLength:(NSNumber*)length; 16 | - (void)setDashOffset:(NSNumber*)offset; 17 | - (void)setOutlineWidth:(NSNumber*)width; 18 | - (void)setZIndex:(NSNumber*)_zIndex; 19 | - (void)setPolylinePoints:(NSArray*)_points; 20 | - (NSMutableArray*)getPoints; 21 | - (YMKPolyline*)getPolyline; 22 | - (YMKPolylineMapObject*)getMapObject; 23 | - (void)setMapObject:(YMKPolylineMapObject*)mapObject; 24 | - (void)setHandled:(BOOL)_handled; 25 | 26 | @end 27 | 28 | #endif /* YamapPolylineView */ 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-yamap", 3 | "version": "4.8.3", 4 | "description": "Yandex.MapKit and Yandex.Geocoder for react-native", 5 | "main": "build/index.js", 6 | "types": "build/index.d.ts", 7 | "scripts": { 8 | "prepublishOnly": "rm -rf build && tsc", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "homepage": "https://github.com/volga-volga/react-native-yamap", 12 | "keywords": [ 13 | "react-native", 14 | "mapkit", 15 | "yandex", 16 | "yamap", 17 | "polygon", 18 | "polyline", 19 | "marker", 20 | "map", 21 | "maps", 22 | "react-native-maps", 23 | "yandexmap", 24 | "yandexmaps", 25 | "geocoder" 26 | ], 27 | "author": "Nikita Sirotkin", 28 | "license": "", 29 | "devDependencies": { 30 | "@types/react": "*", 31 | "@types/react-native": "*", 32 | "typescript": "^4.9.5" 33 | }, 34 | "peerDependencies": { 35 | "react": "*", 36 | "react-native": "*" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/Polygon.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { requireNativeComponent } from 'react-native'; 3 | import { processColorProps } from '../utils'; 4 | import { Point } from '../interfaces'; 5 | 6 | export interface PolygonProps { 7 | fillColor?: string; 8 | strokeColor?: string; 9 | strokeWidth?: number; 10 | zIndex?: number; 11 | onPress?: () => void; 12 | points: Point[]; 13 | innerRings?: (Point[])[]; 14 | children?: undefined; 15 | handled?: boolean; 16 | } 17 | 18 | const NativePolygonComponent = requireNativeComponent('YamapPolygon'); 19 | 20 | export class Polygon extends React.Component { 21 | static defaultProps = { 22 | innerRings: [] 23 | }; 24 | 25 | render() { 26 | const props = { ...this.props }; 27 | 28 | processColorProps(props, 'fillColor' as keyof PolygonProps); 29 | processColorProps(props, 'strokeColor' as keyof PolygonProps); 30 | 31 | return ; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ios/View/YamapMarkerView.h: -------------------------------------------------------------------------------- 1 | #ifndef YamapMarkerView_h 2 | #define YamapMarkerView_h 3 | #import 4 | @import YandexMapsMobile; 5 | 6 | @class RCTBridge; 7 | 8 | @interface YamapMarkerView: UIView 9 | 10 | @property (nonatomic, copy) RCTBubblingEventBlock onPress; 11 | 12 | // PROPS 13 | - (void)setZIndex:(NSNumber*)_zIndex; 14 | - (void)setScale:(NSNumber*)_scale; 15 | - (void)setRotated:(NSNumber*)_rotation; 16 | - (void)setSource:(NSString*)_source; 17 | - (void)setPoint:(YMKPoint*)_points; 18 | - (void)setAnchor:(NSValue*)_anchor; 19 | - (void)setVisible:(NSNumber*)_visible; 20 | - (void)setHandled:(BOOL)_visible; 21 | 22 | // REF 23 | - (void)animatedMoveTo:(YMKPoint*)point withDuration:(float)duration; 24 | - (void)animatedRotateTo:(float)angle withDuration:(float)duration; 25 | - (YMKPoint*)getPoint; 26 | - (YMKPlacemarkMapObject*)getMapObject; 27 | - (void)setMapObject:(YMKPlacemarkMapObject*)mapObject; 28 | - (void)setClusterMapObject:(YMKPlacemarkMapObject*)mapObject; 29 | 30 | @end 31 | 32 | #endif /* YamapMarkerView_h */ 33 | -------------------------------------------------------------------------------- /src/components/Polyline.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { requireNativeComponent } from 'react-native'; 3 | import { processColorProps } from '../utils'; 4 | import { Point } from '../interfaces'; 5 | 6 | export interface PolylineProps { 7 | strokeColor?: string; 8 | outlineColor?: string; 9 | strokeWidth?: number; 10 | outlineWidth?: number; 11 | dashLength?: number; 12 | dashOffset?: number; 13 | gapLength?: number; 14 | zIndex?: number; 15 | onPress?: () => void; 16 | points: Point[]; 17 | children?: undefined; 18 | handled?: boolean; 19 | } 20 | 21 | const NativePolylineComponent = requireNativeComponent('YamapPolyline'); 22 | 23 | export class Polyline extends React.Component { 24 | render() { 25 | const props = { ...this.props }; 26 | 27 | processColorProps(props, 'fillColor' as keyof PolylineProps); 28 | processColorProps(props, 'strokeColor' as keyof PolylineProps); 29 | processColorProps(props, 'outlineColor' as keyof PolylineProps); 30 | 31 | return ; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/search/MapSearchClient.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.search 2 | 3 | import com.yandex.mapkit.geometry.Geometry 4 | import com.yandex.mapkit.geometry.Point 5 | import com.yandex.mapkit.search.SearchOptions 6 | import ru.vvdev.yamap.utils.Callback 7 | 8 | interface MapSearchClient { 9 | fun searchPoint( 10 | point: Point, 11 | zoom: Int, 12 | options: SearchOptions, 13 | onSuccess: Callback, 14 | onError: Callback? 15 | ) 16 | 17 | fun searchAddress( 18 | text: String, 19 | geometry: Geometry, 20 | options: SearchOptions, 21 | onSuccess: Callback, 22 | onError: Callback? 23 | ) 24 | 25 | fun resolveURI( 26 | uri: String, 27 | options: SearchOptions, 28 | onSuccess: Callback, 29 | onError: Callback? 30 | ) 31 | 32 | fun searchByURI( 33 | uri: String, 34 | options: SearchOptions, 35 | onSuccess: Callback, 36 | onError: Callback? 37 | ) 38 | } -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | buildscript { 3 | repositories { 4 | jcenter() 5 | mavenCentral() 6 | } 7 | } 8 | 9 | apply plugin: 'com.android.library' 10 | apply plugin: 'org.jetbrains.kotlin.android' 11 | 12 | def safeExtGet(prop, fallback) { 13 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 14 | } 15 | 16 | def GOOGLE_PLAY_SERVICES_LOCATION_VERSION = safeExtGet('playServicesLocationVersion', "+") 17 | 18 | android { 19 | namespace = 'ru.vvdev.yamap' 20 | 21 | defaultConfig { 22 | compileSdkVersion 34 23 | minSdkVersion 26 24 | targetSdkVersion 34 25 | versionCode 1 26 | versionName "1.1" 27 | } 28 | lintOptions { 29 | abortOnError false 30 | } 31 | kotlinOptions { 32 | jvmTarget = '17' 33 | } 34 | } 35 | 36 | repositories { 37 | mavenCentral() 38 | } 39 | 40 | dependencies { 41 | implementation 'com.google.android.gms:play-services-location:$GOOGLE_PLAY_SERVICES_LOCATION_VERSION' 42 | implementation 'com.facebook.react:react-native:+' 43 | implementation 'com.yandex.android:maps.mobile:4.19.0-full' 44 | implementation 'androidx.core:core-ktx:1.13.1' 45 | } 46 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/RNYamapPackage.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap 2 | 3 | import com.facebook.react.ReactPackage 4 | import com.facebook.react.bridge.NativeModule 5 | import com.facebook.react.bridge.ReactApplicationContext 6 | import com.facebook.react.uimanager.ViewManager 7 | import ru.vvdev.yamap.search.RNYandexSearchModule 8 | import ru.vvdev.yamap.suggest.RNYandexSuggestModule 9 | import java.util.Arrays 10 | 11 | class RNYamapPackage : ReactPackage { 12 | override fun createNativeModules(reactContext: ReactApplicationContext): List { 13 | return Arrays.asList( 14 | RNYamapModule(reactContext), 15 | RNYandexSuggestModule(reactContext), 16 | RNYandexSearchModule(reactContext) 17 | ) 18 | } 19 | 20 | override fun createViewManagers(reactContext: ReactApplicationContext): List> { 21 | return Arrays.asList>( 22 | YamapViewManager(), 23 | ClusteredYamapViewManager(), 24 | YamapPolygonManager(), 25 | YamapPolylineManager(), 26 | YamapMarkerManager(), 27 | YamapCircleManager() 28 | ) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ios/YamapSearch.m: -------------------------------------------------------------------------------- 1 | #import "React/RCTBridgeModule.h" 2 | #import "React/RCTViewManager.h" 3 | #import 4 | 5 | @interface RCT_EXTERN_MODULE(YamapSearch, NSObject) 6 | 7 | RCT_EXTERN_METHOD(searchByAddress:(nonnull NSString*) searchQuery figure:(NSDictionary*)figure options:(NSDictionary*) options resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject) 8 | 9 | RCT_EXTERN_METHOD(searchByPoint:(nonnull NSDictionary*) point zoom:(nonnull NSNumber*) zoom options:(NSDictionary*) options resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject) 10 | 11 | RCT_EXTERN_METHOD(addressToGeo:(nonnull NSString*) searchQuery resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject) 12 | 13 | RCT_EXTERN_METHOD(geoToAddress:(nonnull NSDictionary*) point resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject) 14 | 15 | RCT_EXTERN_METHOD(searchByURI:(nonnull NSString*) searchUri options:(NSDictionary*) options resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject) 16 | 17 | RCT_EXTERN_METHOD(resolveURI:(nonnull NSString*) searchUri options:(NSDictionary*) options resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject) 18 | 19 | @end 20 | 21 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/search/YandexSearchRNArgsHelper.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.search 2 | 3 | import com.facebook.react.bridge.Arguments 4 | import com.facebook.react.bridge.WritableMap 5 | 6 | class YandexSearchRNArgsHelper { 7 | fun createSearchMapFrom(data: MapSearchItem?): WritableMap { 8 | val result = Arguments.createMap() 9 | if (data != null) { 10 | result.putString("formatted", data.formatted) 11 | result.putString("country_code", data.country_code) 12 | val components = Arguments.createArray() 13 | for (i in data.Components!!) { 14 | val mappedItem = Arguments.createMap() 15 | mappedItem.putString("kind", i.kind) 16 | mappedItem.putString("name", i.name) 17 | components.pushMap(mappedItem); 18 | } 19 | result.putArray("Components", components) 20 | result.putString("uri", data.uri); 21 | if (data.point!=null) { 22 | val resultPoint = Arguments.createMap() 23 | resultPoint.putDouble("lat", data.point!!.latitude) 24 | resultPoint.putDouble("lon", data.point!!.longitude) 25 | result.putMap("point", resultPoint); 26 | } 27 | } 28 | return result 29 | } 30 | } -------------------------------------------------------------------------------- /ios/YamapCircle.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import "YamapCircle.h" 5 | #import "RNYamap.h" 6 | 7 | #import "View/YamapCircleView.h" 8 | #import "View/RNYMView.h" 9 | 10 | #import "Converter/RCTConvert+Yamap.m" 11 | 12 | #ifndef MAX 13 | #import 14 | #endif 15 | 16 | @implementation YamapCircle 17 | 18 | RCT_EXPORT_MODULE() 19 | 20 | - (NSArray*)supportedEvents { 21 | return @[@"onPress"]; 22 | } 23 | 24 | - (instancetype)init { 25 | self = [super init]; 26 | 27 | return self; 28 | } 29 | 30 | + (BOOL)requiresMainQueueSetup { 31 | return YES; 32 | } 33 | 34 | - (UIView* _Nullable)view { 35 | return [[YamapCircleView alloc] init]; 36 | } 37 | 38 | // PROPS 39 | RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock) 40 | 41 | RCT_CUSTOM_VIEW_PROPERTY (center, YMKPoint, YamapCircleView) { 42 | if (json != nil) { 43 | YMKPoint* point = [RCTConvert YMKPoint:json]; 44 | [view setCircleCenter: point]; 45 | } 46 | } 47 | 48 | RCT_CUSTOM_VIEW_PROPERTY (radius, NSNumber, YamapCircleView) { 49 | [view setRadius: [json floatValue]]; 50 | } 51 | 52 | RCT_CUSTOM_VIEW_PROPERTY(fillColor, NSNumber, YamapCircleView) { 53 | [view setFillColor: [RCTConvert UIColor:json]]; 54 | } 55 | 56 | RCT_CUSTOM_VIEW_PROPERTY(strokeColor, NSNumber, YamapCircleView) { 57 | [view setStrokeColor: [RCTConvert UIColor:json]]; 58 | } 59 | 60 | RCT_CUSTOM_VIEW_PROPERTY(strokeWidth, NSNumber, YamapCircleView) { 61 | [view setStrokeWidth: json]; 62 | } 63 | 64 | RCT_CUSTOM_VIEW_PROPERTY(zIndex, NSNumber, YamapCircleView) { 65 | [view setZIndex: json]; 66 | } 67 | 68 | RCT_CUSTOM_VIEW_PROPERTY(handled, NSNumber, YamapCircleView) { 69 | if (json == nil || [json boolValue]) { 70 | [view setHandled: YES]; 71 | } else { 72 | [view setHandled: NO]; 73 | } 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/utils/ImageLoader.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.utils 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.BitmapFactory 6 | import android.os.Handler 7 | import android.os.Looper 8 | import java.io.BufferedInputStream 9 | import java.io.IOException 10 | import java.net.URL 11 | 12 | object ImageLoader { 13 | private fun getResId(resName: String, c: Class<*>): Int { 14 | try { 15 | val idField = c.getDeclaredField(resName) 16 | return idField.getInt(idField) 17 | } catch (e: Exception) { 18 | e.printStackTrace() 19 | return -1 20 | } 21 | } 22 | 23 | @Throws(IOException::class) 24 | private fun getBitmap(context: Context, url: String): Bitmap { 25 | if (url.contains("http://") || url.contains("https://")) { 26 | val aURL = URL(url) 27 | val conn = aURL.openConnection() 28 | conn.connect() 29 | val `is` = conn.getInputStream() 30 | val bis = BufferedInputStream(`is`) 31 | val bitmap = BitmapFactory.decodeStream(bis) 32 | bis.close() 33 | `is`.close() 34 | return bitmap 35 | } 36 | val id = context.resources.getIdentifier(url, "drawable", context.packageName) 37 | 38 | return BitmapFactory.decodeResource( 39 | context.resources, 40 | id 41 | ) //getResId(url, R.drawable.class)); 42 | } 43 | 44 | fun DownloadImageBitmap(context: Context, url: String, cb: Callback) { 45 | object : Thread() { 46 | override fun run() { 47 | try { 48 | val bitmap = getBitmap(context, url) 49 | Handler(Looper.getMainLooper()).post { cb.invoke(bitmap) } 50 | } catch (e: Exception) { 51 | e.printStackTrace() 52 | } 53 | } 54 | }.start() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ios/RNYamap.m: -------------------------------------------------------------------------------- 1 | #import "RNYamap.h" 2 | @import YandexMapsMobile; 3 | 4 | @implementation yamap 5 | 6 | static NSString * _pinIcon; 7 | static NSString * _arrowIcon; 8 | static NSString * _markerIcon; 9 | static NSString * _selectedMarkerIcon; 10 | 11 | @synthesize map; 12 | 13 | - (instancetype) init { 14 | self = [super init]; 15 | if (self) { 16 | map = [[YamapView alloc] init]; 17 | } 18 | 19 | return self; 20 | } 21 | 22 | + (BOOL)requiresMainQueueSetup 23 | { 24 | return YES; 25 | } 26 | 27 | - (void)initWithKey:(NSString *) apiKey { 28 | [YMKMapKit setApiKey: apiKey]; 29 | [[YMKMapKit sharedInstance] onStart]; 30 | } 31 | 32 | - (dispatch_queue_t)methodQueue{ 33 | return dispatch_get_main_queue(); 34 | } 35 | 36 | RCT_EXPORT_METHOD(init: (NSString *) apiKey 37 | resolver:(RCTPromiseResolveBlock)resolve 38 | rejecter:(RCTPromiseRejectBlock)reject) { 39 | @try { 40 | [self initWithKey: apiKey]; 41 | resolve(nil); 42 | } @catch (NSException *exception) { 43 | NSError *error = nil; 44 | if (exception.userInfo.count > 0) { 45 | error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:exception.userInfo]; 46 | } 47 | reject(exception.name, exception.reason ?: @"Error initiating YMKMapKit", error); 48 | } 49 | } 50 | 51 | RCT_EXPORT_METHOD(setLocale: (NSString *) locale successCallback:(RCTResponseSenderBlock)successCb errorCallback:(RCTResponseSenderBlock) errorCb) { 52 | [YRTI18nManagerFactory setLocaleWithLocale:locale]; 53 | successCb(@[]); 54 | } 55 | 56 | RCT_EXPORT_METHOD(resetLocale:(RCTResponseSenderBlock)successCb errorCallback:(RCTResponseSenderBlock) errorCb) { 57 | [YRTI18nManagerFactory setLocaleWithLocale:nil]; 58 | successCb(@[]); 59 | } 60 | 61 | RCT_EXPORT_METHOD(getLocale:(RCTResponseSenderBlock)successCb errorCallback:(RCTResponseSenderBlock) errorCb) { 62 | NSString * locale = [YRTI18nManagerFactory getLocale]; 63 | successCb(@[locale]); 64 | } 65 | 66 | RCT_EXPORT_MODULE() 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /ios/YamapPolygon.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import "YamapPolygon.h" 5 | #import "RNYamap.h" 6 | 7 | #import "View/YamapPolygonView.h" 8 | #import "View/RNYMView.h" 9 | 10 | #import "Converter/RCTConvert+Yamap.m" 11 | 12 | #ifndef MAX 13 | #import 14 | #endif 15 | 16 | @implementation YamapPolygon 17 | 18 | RCT_EXPORT_MODULE() 19 | 20 | - (NSArray*)supportedEvents { 21 | return @[@"onPress"]; 22 | } 23 | 24 | - (instancetype)init { 25 | self = [super init]; 26 | 27 | return self; 28 | } 29 | 30 | + (BOOL)requiresMainQueueSetup { 31 | return YES; 32 | } 33 | 34 | - (UIView* _Nullable)view { 35 | return [[YamapPolygonView alloc] init]; 36 | } 37 | 38 | // PROPS 39 | RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock) 40 | 41 | RCT_CUSTOM_VIEW_PROPERTY (points, NSArray, YamapPolygonView) { 42 | if (json != nil) { 43 | [view setPolygonPoints: [RCTConvert Points:json]]; 44 | } 45 | } 46 | 47 | RCT_CUSTOM_VIEW_PROPERTY (innerRings, NSArray>, YamapPolygonView) { 48 | NSMutableArray* innerRings = [[NSMutableArray alloc] init]; 49 | 50 | if (json != nil) { 51 | for (int i = 0; i < [(NSArray *)json count]; ++i) { 52 | [innerRings addObject:[RCTConvert Points:[json objectAtIndex:i]]]; 53 | } 54 | } 55 | 56 | [view setInnerRings: innerRings]; 57 | } 58 | 59 | RCT_CUSTOM_VIEW_PROPERTY(fillColor, NSNumber, YamapPolygonView) { 60 | [view setFillColor: [RCTConvert UIColor:json]]; 61 | } 62 | 63 | RCT_CUSTOM_VIEW_PROPERTY(strokeColor, NSNumber, YamapPolygonView) { 64 | [view setStrokeColor: [RCTConvert UIColor:json]]; 65 | } 66 | 67 | RCT_CUSTOM_VIEW_PROPERTY(strokeWidth, NSNumber, YamapPolygonView) { 68 | [view setStrokeWidth: json]; 69 | } 70 | 71 | RCT_CUSTOM_VIEW_PROPERTY(zIndex, NSNumber, YamapPolygonView) { 72 | [view setZIndex: json]; 73 | } 74 | 75 | RCT_CUSTOM_VIEW_PROPERTY(handled, NSNumber, YamapPolygonView) { 76 | if (json == nil || [json boolValue]) { 77 | [view setHandled: YES]; 78 | } else { 79 | [view setHandled: NO]; 80 | } 81 | } 82 | 83 | @end 84 | -------------------------------------------------------------------------------- /ios/Converter/RCTConvert+Yamap.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | @import YandexMapsMobile; 4 | 5 | @interface RCTConvert(Yamap) 6 | 7 | @end 8 | 9 | @implementation RCTConvert(Yamap) 10 | 11 | + (YMKPoint*)YMKPoint:(id)json { 12 | json = [self NSDictionary:json]; 13 | YMKPoint *target = [YMKPoint pointWithLatitude:[self double:json[@"lat"]] longitude:[self double:json[@"lon"]]]; 14 | 15 | return target; 16 | } 17 | 18 | + (YMKScreenPoint*)YMKScreenPoint:(id)json { 19 | json = [self NSDictionary:json]; 20 | YMKScreenPoint *target = [YMKScreenPoint screenPointWithX:[self float:json[@"x"]] y:[self float:json[@"y"]]]; 21 | 22 | return target; 23 | } 24 | 25 | + (NSArray*)Vehicles:(id)json { 26 | return [self NSArray:json]; 27 | } 28 | 29 | + (NSMutableArray*)Points:(id)json { 30 | NSArray* parsedArray = [self NSArray:json]; 31 | NSMutableArray* result = [[NSMutableArray alloc] init]; 32 | 33 | for (NSDictionary* jsonMarker in parsedArray) { 34 | double lat = [[jsonMarker valueForKey:@"lat"] doubleValue]; 35 | double lon = [[jsonMarker valueForKey:@"lon"] doubleValue]; 36 | YMKPoint *point = [YMKPoint pointWithLatitude:lat longitude:lon]; 37 | [result addObject:point]; 38 | } 39 | 40 | return result; 41 | } 42 | 43 | + (NSMutableArray*)ScreenPoints:(id)json { 44 | NSArray* parsedArray = [self NSArray:json]; 45 | NSMutableArray* result = [[NSMutableArray alloc] init]; 46 | 47 | for (NSDictionary* jsonMarker in parsedArray) { 48 | float x = [[jsonMarker valueForKey:@"x"] floatValue]; 49 | float y = [[jsonMarker valueForKey:@"y"] floatValue]; 50 | YMKScreenPoint *point = [YMKScreenPoint screenPointWithX:x y:y]; 51 | [result addObject:point]; 52 | } 53 | 54 | return result; 55 | } 56 | 57 | + (float)Zoom:(id)json { 58 | json = [self NSDictionary:json]; 59 | return [self float:json[@"zoom"]]; 60 | } 61 | 62 | + (float)Azimuth:(id)json { 63 | json = [self NSDictionary:json]; 64 | return [self float:json[@"azimuth"]]; 65 | } 66 | 67 | + (float)Tilt:(id)json { 68 | json = [self NSDictionary:json]; 69 | return [self float:json[@"tilt"]]; 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /ios/YamapPolyline.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import "YamapPolyline.h" 5 | #import "RNYamap.h" 6 | 7 | #import "View/YamapPolylineView.h" 8 | #import "View/RNYMView.h" 9 | 10 | #import "Converter/RCTConvert+Yamap.m" 11 | 12 | #ifndef MAX 13 | #import 14 | #endif 15 | 16 | @implementation YamapPolyline 17 | 18 | RCT_EXPORT_MODULE() 19 | 20 | - (NSArray*)supportedEvents { 21 | return @[@"onPress"]; 22 | } 23 | 24 | - (instancetype)init { 25 | self = [super init]; 26 | 27 | return self; 28 | } 29 | 30 | + (BOOL)requiresMainQueueSetup { 31 | return YES; 32 | } 33 | 34 | - (UIView *_Nullable)view { 35 | return [[YamapPolylineView alloc] init]; 36 | } 37 | 38 | // PROPS 39 | RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock) 40 | 41 | RCT_CUSTOM_VIEW_PROPERTY (points, NSArray, YamapPolylineView) { 42 | if (json != nil) { 43 | [view setPolylinePoints: [RCTConvert Points:json]]; 44 | } 45 | } 46 | 47 | RCT_CUSTOM_VIEW_PROPERTY(outlineColor, NSNumber, YamapPolylineView) { 48 | [view setOutlineColor: [RCTConvert UIColor:json]]; 49 | } 50 | 51 | RCT_CUSTOM_VIEW_PROPERTY(strokeColor, NSNumber, YamapPolylineView) { 52 | [view setStrokeColor: [RCTConvert UIColor:json]]; 53 | } 54 | 55 | RCT_CUSTOM_VIEW_PROPERTY(strokeWidth, NSNumber, YamapPolylineView) { 56 | [view setStrokeWidth: json]; 57 | } 58 | 59 | RCT_CUSTOM_VIEW_PROPERTY(dashLength, NSNumber, YamapPolylineView) { 60 | [view setDashLength: json]; 61 | } 62 | 63 | RCT_CUSTOM_VIEW_PROPERTY(gapLength, NSNumber, YamapPolylineView) { 64 | [view setGapLength: json]; 65 | } 66 | 67 | RCT_CUSTOM_VIEW_PROPERTY(dashOffset, NSNumber, YamapPolylineView) { 68 | [view setDashOffset: json]; 69 | } 70 | 71 | RCT_CUSTOM_VIEW_PROPERTY(outlineWidth, NSNumber, YamapPolylineView) { 72 | [view setOutlineWidth: json]; 73 | } 74 | 75 | RCT_CUSTOM_VIEW_PROPERTY(zIndex, NSNumber, YamapPolylineView) { 76 | [view setZIndex: json]; 77 | } 78 | 79 | RCT_CUSTOM_VIEW_PROPERTY(handled, NSNumber, YamapPolylineView) { 80 | if (json == nil || [json boolValue]) { 81 | [view setHandled: YES]; 82 | } else { 83 | [view setHandled: NO]; 84 | } 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Point { 2 | lat: number; 3 | lon: number; 4 | } 5 | 6 | export interface BoundingBox { 7 | southWest: Point; 8 | northEast: Point; 9 | } 10 | 11 | export interface ScreenPoint { 12 | x: number; 13 | y: number; 14 | } 15 | 16 | export interface MapLoaded { 17 | renderObjectCount: number; 18 | curZoomModelsLoaded: number; 19 | curZoomPlacemarksLoaded: number; 20 | curZoomLabelsLoaded: number; 21 | curZoomGeometryLoaded: number; 22 | tileMemoryUsage: number; 23 | delayedGeometryLoaded: number; 24 | fullyAppeared: number; 25 | fullyLoaded: number; 26 | } 27 | 28 | export interface InitialRegion { 29 | lat: number; 30 | lon: number; 31 | zoom?: number; 32 | azimuth?: number; 33 | tilt?: number; 34 | } 35 | 36 | export type MasstransitVehicles = 'bus' | 'trolleybus' | 'tramway' | 'minibus' | 'suburban' | 'underground' | 'ferry' | 'cable' | 'funicular'; 37 | 38 | export type Vehicles = MasstransitVehicles | 'walk' | 'car'; 39 | 40 | export interface DrivingInfo { 41 | time: string; 42 | timeWithTraffic: string; 43 | distance: number; 44 | } 45 | 46 | export interface MasstransitInfo { 47 | time: string; 48 | transferCount: number; 49 | walkingDistance: number; 50 | } 51 | 52 | export interface RouteInfo { 53 | id: string; 54 | sections: { 55 | points: Point[]; 56 | sectionInfo: T; 57 | routeInfo: T; 58 | routeIndex: number; 59 | stops: any[]; 60 | type: string; 61 | transports?: any; 62 | sectionColor?: string; 63 | }[]; 64 | } 65 | 66 | export interface RoutesFoundEvent { 67 | nativeEvent: { 68 | status: 'success' | 'error'; 69 | id: string; 70 | routes: RouteInfo[]; 71 | }; 72 | } 73 | 74 | export interface CameraPosition { 75 | zoom: number; 76 | tilt: number; 77 | azimuth: number; 78 | point: Point; 79 | reason: 'GESTURES' | 'APPLICATION'; 80 | finished: boolean; 81 | } 82 | 83 | export type VisibleRegion = { 84 | bottomLeft: Point; 85 | bottomRight: Point; 86 | topLeft: Point; 87 | topRight: Point; 88 | } 89 | 90 | export enum Animation { 91 | SMOOTH, 92 | LINEAR 93 | } 94 | 95 | export type YandexLogoPosition = { 96 | horizontal?: 'left' | 'center' | 'right'; 97 | vertical?: 'top' | 'bottom'; 98 | } 99 | 100 | export type YandexLogoPadding = { 101 | horizontal?: number; 102 | vertical?: number; 103 | } 104 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/YamapCircleManager.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap 2 | 3 | import com.facebook.react.bridge.ReadableMap 4 | import com.facebook.react.common.MapBuilder 5 | import com.facebook.react.uimanager.ThemedReactContext 6 | import com.facebook.react.uimanager.ViewGroupManager 7 | import com.facebook.react.uimanager.annotations.ReactProp 8 | import com.yandex.mapkit.geometry.Point 9 | import ru.vvdev.yamap.view.YamapCircle 10 | import javax.annotation.Nonnull 11 | 12 | class YamapCircleManager internal constructor() : ViewGroupManager() { 13 | override fun getName(): String { 14 | return REACT_CLASS 15 | } 16 | 17 | override fun getExportedCustomDirectEventTypeConstants(): Map? { 18 | return MapBuilder.builder() 19 | .put("onPress", MapBuilder.of("registrationName", "onPress")) 20 | .build() 21 | } 22 | 23 | override fun getExportedCustomBubblingEventTypeConstants(): MutableMap? { 24 | return MapBuilder.builder() 25 | .build() 26 | } 27 | 28 | @Nonnull 29 | public override fun createViewInstance(@Nonnull context: ThemedReactContext): YamapCircle { 30 | return YamapCircle(context) 31 | } 32 | 33 | // PROPS 34 | @ReactProp(name = "center") 35 | fun setCenter(view: YamapCircle, center: ReadableMap?) { 36 | if (center != null) { 37 | val lon = center.getDouble("lon") 38 | val lat = center.getDouble("lat") 39 | val point = Point(lat, lon) 40 | view.setCenter(point) 41 | } 42 | } 43 | 44 | @ReactProp(name = "radius") 45 | fun setRadius(view: YamapCircle, radius: Float) { 46 | view.setRadius(radius) 47 | } 48 | 49 | @ReactProp(name = "strokeWidth") 50 | fun setStrokeWidth(view: YamapCircle, width: Float) { 51 | view.setStrokeWidth(width) 52 | } 53 | 54 | @ReactProp(name = "strokeColor") 55 | fun setStrokeColor(view: YamapCircle, color: Int) { 56 | view.setStrokeColor(color) 57 | } 58 | 59 | @ReactProp(name = "fillColor") 60 | fun setFillColor(view: YamapCircle, color: Int) { 61 | view.setFillColor(color) 62 | } 63 | 64 | @ReactProp(name = "zIndex") 65 | fun setZIndex(view: YamapCircle, zIndex: Int) { 66 | view.setZIndex(zIndex) 67 | } 68 | 69 | @ReactProp(name = "handled") 70 | fun setHandled(view: YamapCircle, handled: Boolean?) { 71 | view.setHandled(handled ?: true) 72 | } 73 | 74 | companion object { 75 | const val REACT_CLASS: String = "YamapCircle" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/components/Marker.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { requireNativeComponent, Platform, ImageSourcePropType, UIManager, findNodeHandle } from 'react-native'; 3 | // @ts-ignore 4 | import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; 5 | import { Point } from '../interfaces'; 6 | 7 | export interface MarkerProps { 8 | children?: React.ReactElement; 9 | zIndex?: number; 10 | scale?: number; 11 | rotated?: boolean; 12 | onPress?: () => void; 13 | point: Point; 14 | source?: ImageSourcePropType; 15 | anchor?: { x: number, y: number }; 16 | visible?: boolean; 17 | handled?: boolean; 18 | } 19 | 20 | const NativeMarkerComponent = requireNativeComponent('YamapMarker'); 21 | 22 | interface State { 23 | recreateKey: boolean; 24 | children: any; 25 | } 26 | 27 | export class Marker extends React.Component { 28 | static defaultProps = { 29 | rotated: false, 30 | }; 31 | 32 | state = { 33 | recreateKey: false, 34 | children: this.props.children 35 | }; 36 | 37 | private getCommand(cmd: string): any { 38 | if (Platform.OS === 'ios') { 39 | return UIManager.getViewManagerConfig('YamapMarker').Commands[cmd]; 40 | } else { 41 | return cmd; 42 | } 43 | } 44 | 45 | static getDerivedStateFromProps(nextProps: MarkerProps, prevState: State): Partial { 46 | if (Platform.OS === 'ios') { 47 | return { 48 | children: nextProps.children, 49 | recreateKey: 50 | nextProps.children === prevState.children 51 | ? prevState.recreateKey 52 | : !prevState.recreateKey 53 | }; 54 | } 55 | 56 | return { 57 | children: nextProps.children, 58 | recreateKey: Boolean(nextProps.children) 59 | }; 60 | } 61 | 62 | private resolveImageUri(img?: ImageSourcePropType) { 63 | return img ? resolveAssetSource(img).uri : ''; 64 | } 65 | 66 | private getProps() { 67 | return { 68 | ...this.props, 69 | source: this.resolveImageUri(this.props.source) 70 | }; 71 | } 72 | 73 | public animatedMoveTo(coords: Point, duration: number) { 74 | UIManager.dispatchViewManagerCommand( 75 | findNodeHandle(this), 76 | this.getCommand('animatedMoveTo'), 77 | [coords, duration] 78 | ); 79 | } 80 | 81 | public animatedRotateTo(angle: number, duration: number) { 82 | UIManager.dispatchViewManagerCommand( 83 | findNodeHandle(this), 84 | this.getCommand('animatedRotateTo'), 85 | [angle, duration] 86 | ); 87 | } 88 | 89 | render() { 90 | return ( 91 | 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Suggest.ts: -------------------------------------------------------------------------------- 1 | import { BoundingBox, Point } from './interfaces'; 2 | import { NativeModules } from 'react-native'; 3 | 4 | const { YamapSuggests } = NativeModules; 5 | 6 | export type YamapSuggest = { 7 | title: string; 8 | subtitle?: string; 9 | uri?: string; 10 | }; 11 | export type YamapCoords = { 12 | lon: number; 13 | lat: number; 14 | }; 15 | export type YamapSuggestWithCoords = YamapSuggest & Partial; 16 | 17 | export enum SuggestTypes { 18 | YMKSuggestTypeUnspecified = 0b00, 19 | /** 20 | * Toponyms. 21 | */ 22 | YMKSuggestTypeGeo = 0b01, 23 | /** 24 | * Companies. 25 | */ 26 | YMKSuggestTypeBiz = 0b01 << 1, 27 | /** 28 | * Mass transit routes. 29 | */ 30 | YMKSuggestTypeTransit = 0b01 << 2, 31 | } 32 | 33 | export type SuggestOptions = { 34 | userPosition?: Point; 35 | boundingBox?: BoundingBox; 36 | suggestWords?: boolean; 37 | suggestTypes?: SuggestTypes[]; 38 | }; 39 | 40 | type SuggestFetcher = (query: string, options?: SuggestOptions) => Promise>; 41 | const suggest: SuggestFetcher = (query, options) => { 42 | if (options) { 43 | return YamapSuggests.suggestWithOptions(query, options); 44 | } 45 | return YamapSuggests.suggest(query); 46 | } 47 | 48 | type SuggestWithCoordsFetcher = (query: string, options?: SuggestOptions) => Promise>; 49 | const suggestWithCoords: SuggestWithCoordsFetcher = async (query, options) => { 50 | const suggests = await suggest(query, options); 51 | 52 | return suggests.map((item) => ({ 53 | ...item, 54 | ...getCoordsFromSuggest(item), 55 | })); 56 | }; 57 | 58 | type SuggestResetter = () => Promise; 59 | const reset: SuggestResetter = () => YamapSuggests.reset(); 60 | // Original uri format for the MapKit 4.0.0 ymapsbm1://geo?ll=39.957371%2C48.306156&spn=0.001000%2C0.001000&text=%D0%A0%D0%BE%D1%81%D1%81%D0%B8%D1%8F%2C%20%D0%A0%D0%BE%D1%81%D1%82%D0%BE%D0%B2%D1%81%D0%BA%D0%B0%D1%8F%20%D0%BE%D0%B1%D0%BB%D0%B0%D1%81%D1%82%D1%8C%2C%20%D0%94%D0%BE%D0%BD%D0%B5%D1%86%D0%BA%2C%20%D1%83%D0%BB%D0%B8%D1%86%D0%B0%20%D0%9C%D0%B8%D0%BA%D0%BE%D1%8F%D0%BD%D0%B0%2C%2012 61 | // Decoded uri format ymapsbm1://geo?ll=39.957371,48.306156&spn=0.001000,0.001000&text=Россия, Ростовская область, Донецк, улица Микояна, 12 62 | type LatLonGetter = (suggest: YamapSuggest) => YamapCoords | undefined; 63 | const getCoordsFromSuggest: LatLonGetter = (suggest) => { 64 | const coords = suggest.uri 65 | ?.split('?')[1] 66 | ?.split('&') 67 | ?.find((param) => param.startsWith('ll')) 68 | ?.split('=')[1]; 69 | if (!coords) return; 70 | 71 | const splittedCoords = coords.split('%2C'); 72 | const lon = Number(splittedCoords[0]); 73 | const lat = Number(splittedCoords[1]); 74 | 75 | return { lat, lon }; 76 | }; 77 | 78 | const Suggest = { 79 | suggest, 80 | suggestWithCoords, 81 | reset, 82 | getCoordsFromSuggest 83 | }; 84 | 85 | export default Suggest; 86 | -------------------------------------------------------------------------------- /ios/View/RNYMView.h: -------------------------------------------------------------------------------- 1 | #ifndef RNYMView_h 2 | #define RNYMView_h 3 | #import 4 | #import 5 | @import YandexMapsMobile; 6 | 7 | @class RCTBridge; 8 | 9 | @interface RNYMView: YMKMapView 10 | 11 | @property (nonatomic, assign) CGRect mapFrame; 12 | @property (nonatomic, copy) RCTBubblingEventBlock _Nullable onRouteFound; 13 | @property (nonatomic, copy) RCTBubblingEventBlock _Nullable onCameraPositionReceived; 14 | @property (nonatomic, copy) RCTBubblingEventBlock _Nullable onVisibleRegionReceived; 15 | @property (nonatomic, copy) RCTBubblingEventBlock _Nullable onCameraPositionChange; 16 | @property (nonatomic, copy) RCTBubblingEventBlock _Nullable onCameraPositionChangeEnd; 17 | @property (nonatomic, copy) RCTBubblingEventBlock _Nullable onMapPress; 18 | @property (nonatomic, copy) RCTBubblingEventBlock _Nullable onMapLongPress; 19 | @property (nonatomic, copy) RCTBubblingEventBlock _Nullable onMapLoaded; 20 | @property (nonatomic, copy) RCTBubblingEventBlock _Nullable onWorldToScreenPointsReceived; 21 | @property (nonatomic, copy) RCTBubblingEventBlock _Nullable onScreenToWorldPointsReceived; 22 | 23 | // REF 24 | - (void)emitCameraPositionToJS:(NSString *_Nonnull)_id; 25 | - (void)emitVisibleRegionToJS:(NSString *_Nonnull)_id; 26 | - (void)setCenter:(YMKCameraPosition *_Nonnull)position withDuration:(float)duration withAnimation:(int)animation; 27 | - (void)setZoom:(float)zoom withDuration:(float)duration withAnimation:(int)animation; 28 | - (void)fitAllMarkers; 29 | - (void)fitMarkers:(NSArray *_Nonnull)points; 30 | - (void)findRoutes:(NSArray *_Nonnull)points vehicles:(NSArray *_Nonnull)vehicles withId:(NSString *_Nonnull)_id; 31 | - (void)setTrafficVisible:(BOOL)traffic; 32 | - (void)emitWorldToScreenPoint:(NSArray *_Nonnull)points withId:(NSString*_Nonnull)_id; 33 | - (void)emitScreenToWorldPoint:(NSArray *_Nonnull)points withId:(NSString*_Nonnull)_id; 34 | - (YMKBoundingBox *_Nonnull)calculateBoundingBox:(NSArray *_Nonnull)points; 35 | 36 | // PROPS 37 | - (void)setNightMode:(BOOL)nightMode; 38 | - (void)setClusters:(BOOL)userClusters; 39 | - (void)setListenUserLocation:(BOOL)listen; 40 | - (void)setUserLocationIcon:(NSString *_Nullable)iconSource; 41 | - (void)setUserLocationIconScale:(NSNumber *_Nullable)iconScale; 42 | - (void)setUserLocationAccuracyFillColor:(UIColor *_Nullable)color; 43 | - (void)setUserLocationAccuracyStrokeColor:(UIColor *_Nullable)color; 44 | - (void)setUserLocationAccuracyStrokeWidth:(float)width; 45 | - (void)setMapType:(NSString *_Nullable)type; 46 | - (void)setInitialRegion:(NSDictionary *_Nullable)initialRegion; 47 | - (void)setMaxFps:(float)maxFps; 48 | - (void)setInteractive:(BOOL)interactive; 49 | - (void)insertMarkerReactSubview:(UIView *_Nullable)subview atIndex:(NSInteger)atIndex; 50 | - (void)removeMarkerReactSubview:(UIView *_Nullable)subview; 51 | - (void)setFollowUser:(BOOL)follow; 52 | - (void)setLogoPosition:(NSDictionary *_Nullable)logoPosition; 53 | - (void)setLogoPadding:(NSDictionary *_Nullable)logoPadding; 54 | 55 | @end 56 | 57 | #endif /* RNYMView_h */ 58 | -------------------------------------------------------------------------------- /src/Search.ts: -------------------------------------------------------------------------------- 1 | import { BoundingBox, Point } from './interfaces'; 2 | import { NativeModules } from 'react-native'; 3 | import { Address } from './geocoding'; 4 | 5 | const { YamapSearch } = NativeModules; 6 | 7 | export type YamapSearch = { 8 | title: string; 9 | subtitle?: string; 10 | uri?: string; 11 | }; 12 | export type YamapCoords = { 13 | lon: number; 14 | lat: number; 15 | }; 16 | export type YamapSearchWithCoords = YamapSearch & Partial; 17 | 18 | export enum SearchTypes { 19 | YMKSearchTypeUnspecified = 0b00, 20 | /** 21 | * Toponyms. 22 | */ 23 | YMKSearchTypeGeo = 0b01, 24 | /** 25 | * Companies. 26 | */ 27 | YMKSearchTypeBiz = 0b01 << 1, 28 | /** 29 | * Mass transit routes. 30 | */ 31 | } 32 | 33 | export enum SearchTypesSnippets { 34 | YMKSearchTypeUnspecified = 0b00, 35 | /** 36 | * Toponyms. 37 | */ 38 | YMKSearchTypeGeo = 0b01, 39 | /** 40 | * Companies. 41 | */ 42 | YMKSearchTypeBiz = 0b01 << 32, 43 | /** 44 | * Mass transit routes. 45 | */ 46 | } 47 | 48 | export type SearchOptions = { 49 | disableSpellingCorrection?: boolean; 50 | geometry?: boolean; 51 | snippets?: SearchTypesSnippets; 52 | searchTypes?: SearchTypes; 53 | }; 54 | 55 | export enum GeoFigureType { 56 | POINT="POINT", 57 | BOUNDINGBOX="BOUNDINGBOX", 58 | POLYLINE="POLYLINE", 59 | POLYGON="POLYGON", 60 | } 61 | 62 | export interface PointParams { 63 | type: GeoFigureType.POINT 64 | value: Point 65 | } 66 | 67 | export interface BoundingBoxParams { 68 | type: GeoFigureType.BOUNDINGBOX 69 | value: BoundingBox 70 | } 71 | 72 | export interface PolylineParams { 73 | type: GeoFigureType.POLYLINE 74 | value: PolylineParams 75 | } 76 | 77 | export interface PolygonParams { 78 | type: GeoFigureType.POLYGON 79 | value: PolygonParams 80 | } 81 | 82 | type FigureParams = PointParams | BoundingBoxParams | PolylineParams | PolygonParams 83 | 84 | type SearchFetcher = (query: string, options?: SearchOptions) => Promise>; 85 | type SearchPointFetcher = (point: Point, options?: SearchOptions) => Promise
; 86 | const searchText = (query: string, figure?: FigureParams, options?: SearchOptions) => { 87 | return YamapSearch.searchByAddress(query, figure, options); 88 | } 89 | 90 | const searchPoint = (point: Point, zoom?: number, options?: SearchOptions): Promise => { 91 | return YamapSearch.searchByPoint(point, zoom, options); 92 | } 93 | 94 | const resolveURI: SearchFetcher = (uri: string, options) => { 95 | return YamapSearch.resolveURI(uri, options); 96 | } 97 | 98 | const searchByURI: SearchFetcher = (uri: string, options) => { 99 | return YamapSearch.searchByURI(uri, options); 100 | } 101 | 102 | const geocodePoint: SearchPointFetcher = (point: Point) => { 103 | return YamapSearch.geoToAddress(point); 104 | } 105 | 106 | const geocodeAddress: SearchFetcher = (address: string) => { 107 | return YamapSearch.addressToGeo(address); 108 | } 109 | 110 | const Search = { 111 | searchText, 112 | searchPoint, 113 | geocodePoint, 114 | geocodeAddress, 115 | resolveURI, 116 | searchByURI 117 | }; 118 | 119 | export default Search; 120 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/RNYamapModule.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap 2 | 3 | import com.facebook.react.bridge.Callback 4 | import com.facebook.react.bridge.Promise 5 | import com.facebook.react.bridge.ReactApplicationContext 6 | import com.facebook.react.bridge.ReactContextBaseJavaModule 7 | import com.facebook.react.bridge.ReactMethod 8 | import com.facebook.react.bridge.UiThreadUtil 9 | import com.facebook.react.bridge.WritableMap 10 | import com.facebook.react.modules.core.DeviceEventManagerModule 11 | import com.yandex.mapkit.MapKitFactory 12 | import com.yandex.runtime.i18n.I18nManagerFactory 13 | 14 | class RNYamapModule internal constructor(context: ReactApplicationContext?) : 15 | ReactContextBaseJavaModule(context) { 16 | init { 17 | Companion.context = context 18 | } 19 | 20 | override fun getName(): String { 21 | return REACT_CLASS 22 | } 23 | 24 | override fun getConstants(): Map? { 25 | return HashMap() 26 | } 27 | 28 | @ReactMethod 29 | fun init(apiKey: String?, promise: Promise) { 30 | UiThreadUtil.runOnUiThread(Thread(Runnable { 31 | var apiKeyException: Throwable? = null 32 | try { 33 | // In case when android application reloads during development 34 | // MapKitFactory is already initialized 35 | // And setting api key leads to crash 36 | try { 37 | MapKitFactory.setApiKey(apiKey!!) 38 | } catch (exception: Throwable) { 39 | apiKeyException = exception 40 | } 41 | 42 | MapKitFactory.initialize(context) 43 | MapKitFactory.getInstance().onStart() 44 | promise.resolve(null) 45 | } catch (exception: Exception) { 46 | if (apiKeyException != null) { 47 | promise.reject(apiKeyException) 48 | return@Runnable 49 | } 50 | promise.reject(exception) 51 | } 52 | })) 53 | } 54 | 55 | @ReactMethod 56 | fun setLocale(locale: String?, successCb: Callback, errorCb: Callback?) { 57 | UiThreadUtil.runOnUiThread(Thread { 58 | MapKitFactory.setLocale(locale) 59 | successCb.invoke() 60 | }) 61 | } 62 | 63 | @ReactMethod 64 | fun getLocale(successCb: Callback, errorCb: Callback?) { 65 | UiThreadUtil.runOnUiThread(Thread { 66 | val locale = I18nManagerFactory.getLocale() 67 | successCb.invoke(locale) 68 | }) 69 | } 70 | 71 | @ReactMethod 72 | fun resetLocale(successCb: Callback, errorCb: Callback?) { 73 | UiThreadUtil.runOnUiThread(Thread { 74 | I18nManagerFactory.setLocale(null) 75 | successCb.invoke() 76 | }) 77 | } 78 | 79 | companion object { 80 | private const val REACT_CLASS = "yamap" 81 | 82 | private var context: ReactApplicationContext? = null 83 | 84 | private fun emitDeviceEvent(eventName: String, eventData: WritableMap?) { 85 | context!!.getJSModule( 86 | DeviceEventManagerModule.RCTDeviceEventEmitter::class.java 87 | ).emit(eventName, eventData) 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ios/View/YamapCircleView.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import 4 | @import YandexMapsMobile; 5 | 6 | #ifndef MAX 7 | #import 8 | #endif 9 | 10 | #import "YamapCircleView.h" 11 | 12 | 13 | #define ANDROID_COLOR(c) [UIColor colorWithRed:((c>>16)&0xFF)/255.0 green:((c>>8)&0xFF)/255.0 blue:((c)&0xFF)/255.0 alpha:((c>>24)&0xFF)/255.0] 14 | 15 | #define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0] 16 | 17 | @implementation YamapCircleView { 18 | YMKPoint* center; 19 | float radius; 20 | YMKCircleMapObject* mapObject; 21 | YMKCircle* circle; 22 | UIColor* fillColor; 23 | UIColor* strokeColor; 24 | NSNumber* strokeWidth; 25 | NSNumber* zIndex; 26 | BOOL handled; 27 | } 28 | 29 | - (instancetype)init { 30 | self = [super init]; 31 | fillColor = UIColor.blackColor; 32 | strokeColor = UIColor.blackColor; 33 | zIndex = [[NSNumber alloc] initWithInt:1]; 34 | strokeWidth = [[NSNumber alloc] initWithInt:1]; 35 | handled = YES; 36 | center = [YMKPoint pointWithLatitude:0 longitude:0]; 37 | radius = 0.f; 38 | circle = [YMKCircle circleWithCenter:center radius:radius]; 39 | 40 | return self; 41 | } 42 | 43 | - (void)updateCircle { 44 | if (mapObject != nil) { 45 | [mapObject setGeometry:circle]; 46 | [mapObject setZIndex:[zIndex floatValue]]; 47 | [mapObject setFillColor:fillColor]; 48 | [mapObject setStrokeColor:strokeColor]; 49 | [mapObject setStrokeWidth:[strokeWidth floatValue]]; 50 | } 51 | } 52 | 53 | - (void)setFillColor:(UIColor*)color { 54 | fillColor = color; 55 | [self updateCircle]; 56 | } 57 | 58 | - (void)setStrokeColor:(UIColor*)color { 59 | strokeColor = color; 60 | [self updateCircle]; 61 | } 62 | 63 | - (void)setHandled:(BOOL)_handled { 64 | handled = _handled; 65 | } 66 | 67 | 68 | - (void)setStrokeWidth:(NSNumber*)width { 69 | strokeWidth = width; 70 | [self updateCircle]; 71 | } 72 | 73 | - (void)setZIndex:(NSNumber*)_zIndex { 74 | zIndex = _zIndex; 75 | [self updateCircle]; 76 | } 77 | 78 | - (void)updateGeometry { 79 | if (center) { 80 | circle = [YMKCircle circleWithCenter:center radius:radius]; 81 | } 82 | } 83 | 84 | - (void)setCircleCenter:(YMKPoint*)point { 85 | center = point; 86 | [self updateGeometry]; 87 | [self updateCircle]; 88 | } 89 | 90 | - (void)setRadius:(float)_radius { 91 | radius = _radius; 92 | [self updateGeometry]; 93 | [self updateCircle]; 94 | } 95 | 96 | - (void)setMapObject:(YMKCircleMapObject*)_mapObject { 97 | mapObject = _mapObject; 98 | [mapObject addTapListenerWithTapListener:self]; 99 | [self updateCircle]; 100 | } 101 | 102 | - (YMKCircle*)getCircle { 103 | return circle; 104 | } 105 | 106 | - (BOOL)onMapObjectTapWithMapObject:(nonnull YMKMapObject*)mapObject point:(nonnull YMKPoint*)point { 107 | if (self.onPress) 108 | self.onPress(@{}); 109 | 110 | return handled; 111 | } 112 | 113 | - (YMKCircleMapObject*)getMapObject { 114 | return mapObject; 115 | } 116 | 117 | @end 118 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/suggest/RNYandexSuggestModule.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.suggest 2 | 3 | import android.content.Context 4 | import com.facebook.react.bridge.Promise 5 | import com.facebook.react.bridge.ReactApplicationContext 6 | import com.facebook.react.bridge.ReactContextBaseJavaModule 7 | import com.facebook.react.bridge.ReactMethod 8 | import com.facebook.react.bridge.ReadableMap 9 | import com.facebook.react.bridge.UiThreadUtil 10 | import ru.vvdev.yamap.utils.Callback 11 | 12 | class RNYandexSuggestModule(reactContext: ReactApplicationContext?) : 13 | ReactContextBaseJavaModule(reactContext) { 14 | private var suggestClient: MapSuggestClient? = null 15 | private val argsHelper = YandexSuggestRNArgsHelper() 16 | 17 | override fun getName(): String { 18 | return "YamapSuggests" 19 | } 20 | 21 | @ReactMethod 22 | fun suggest(text: String?, promise: Promise) { 23 | if (text == null) { 24 | promise.reject(ERR_NO_REQUEST_ARG, "suggest request: text arg is not provided") 25 | return 26 | } 27 | 28 | UiThreadUtil.runOnUiThread { 29 | getSuggestClient(reactApplicationContext).suggest(text, 30 | object : Callback?> { 31 | override fun invoke(arg: List?) { 32 | promise.resolve(argsHelper.createSuggestsMapFrom(arg)) 33 | } 34 | }, 35 | object : Callback { 36 | override fun invoke(arg: Throwable?) { 37 | promise.reject(ERR_SUGGEST_FAILED, "suggest request: " + arg?.message) 38 | } 39 | } 40 | ) 41 | } 42 | } 43 | 44 | @ReactMethod 45 | fun suggestWithOptions(text: String?, options: ReadableMap?, promise: Promise) { 46 | if (text == null) { 47 | promise.reject(ERR_NO_REQUEST_ARG, "suggest request: text arg is not provided") 48 | return 49 | } 50 | 51 | UiThreadUtil.runOnUiThread { 52 | getSuggestClient(reactApplicationContext).suggest(text, options, 53 | object : Callback?> { 54 | override fun invoke(arg: List?) { 55 | promise.resolve(argsHelper.createSuggestsMapFrom(arg)) 56 | } 57 | }, 58 | object : Callback { 59 | override fun invoke(arg: Throwable?) { 60 | promise.reject(ERR_SUGGEST_FAILED, "suggest request: " + arg?.message) 61 | } 62 | } 63 | ) 64 | } 65 | } 66 | 67 | @ReactMethod 68 | fun reset() { 69 | UiThreadUtil.runOnUiThread { getSuggestClient(reactApplicationContext).resetSuggest() } 70 | } 71 | 72 | private fun getSuggestClient(context: Context): MapSuggestClient { 73 | if (suggestClient == null) { 74 | suggestClient = YandexMapSuggestClient(context) 75 | } 76 | 77 | return suggestClient as MapSuggestClient 78 | } 79 | 80 | companion object { 81 | private const val ERR_NO_REQUEST_ARG = "YANDEX_SUGGEST_ERR_NO_REQUEST_ARG" 82 | private const val ERR_SUGGEST_FAILED = "YANDEX_SUGGEST_ERR_SUGGEST_FAILED" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/view/YamapCircle.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.view 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import android.view.ViewGroup 6 | import com.facebook.react.bridge.Arguments 7 | import com.facebook.react.bridge.ReactContext 8 | import com.facebook.react.uimanager.events.RCTEventEmitter 9 | import com.yandex.mapkit.geometry.Circle 10 | import com.yandex.mapkit.geometry.Point 11 | import com.yandex.mapkit.map.CircleMapObject 12 | import com.yandex.mapkit.map.MapObject 13 | import com.yandex.mapkit.map.MapObjectTapListener 14 | import ru.vvdev.yamap.models.ReactMapObject 15 | 16 | class YamapCircle(context: Context?) : ViewGroup(context), MapObjectTapListener, ReactMapObject { 17 | @JvmField 18 | var circle: Circle 19 | 20 | override var rnMapObject: MapObject? = null 21 | private var handled = true 22 | private var fillColor = Color.BLACK 23 | private var strokeColor = Color.BLACK 24 | private var zIndex = 1 25 | private var strokeWidth = 1f 26 | private var center = Point(0.0, 0.0) 27 | private var radius = 0f 28 | 29 | init { 30 | circle = Circle(center, radius) 31 | } 32 | 33 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { 34 | } 35 | 36 | // PROPS 37 | fun setCenter(point: Point) { 38 | center = point 39 | updateGeometry() 40 | updateCircle() 41 | } 42 | 43 | fun setRadius(_radius: Float) { 44 | radius = _radius 45 | updateGeometry() 46 | updateCircle() 47 | } 48 | 49 | private fun updateGeometry() { 50 | circle = Circle(center, radius) 51 | } 52 | 53 | fun setZIndex(_zIndex: Int) { 54 | zIndex = _zIndex 55 | updateCircle() 56 | } 57 | 58 | fun setHandled(_handled: Boolean) { 59 | handled = _handled 60 | } 61 | 62 | fun setStrokeColor(_color: Int) { 63 | strokeColor = _color 64 | updateCircle() 65 | } 66 | 67 | fun setFillColor(_color: Int) { 68 | fillColor = _color 69 | updateCircle() 70 | } 71 | 72 | fun setStrokeWidth(width: Float) { 73 | strokeWidth = width 74 | updateCircle() 75 | } 76 | 77 | private fun updateCircle() { 78 | if (rnMapObject != null) { 79 | (rnMapObject as CircleMapObject).geometry = circle 80 | (rnMapObject as CircleMapObject).strokeWidth = strokeWidth 81 | (rnMapObject as CircleMapObject).strokeColor = strokeColor 82 | (rnMapObject as CircleMapObject).fillColor = fillColor 83 | (rnMapObject as CircleMapObject).zIndex = zIndex.toFloat() 84 | } 85 | } 86 | 87 | fun setCircleMapObject(obj: MapObject?) { 88 | rnMapObject = obj as CircleMapObject? 89 | rnMapObject!!.addTapListener(this) 90 | updateCircle() 91 | } 92 | 93 | // fun setRnMapObject(obj: MapObject?) { 94 | // rnMapObject = obj as CircleMapObject? 95 | // rnMapObject!!.addTapListener(this) 96 | // updateCircle() 97 | // } 98 | 99 | override fun onMapObjectTap(mapObject: MapObject, point: Point): Boolean { 100 | val e = Arguments.createMap() 101 | (context as ReactContext).getJSModule(RCTEventEmitter::class.java).receiveEvent( 102 | id, "onPress", e 103 | ) 104 | 105 | return handled 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/geocoding/index.ts: -------------------------------------------------------------------------------- 1 | import Query from './Query'; 2 | import { GeocodingApiError } from './GeocodingApiError'; 3 | import { Point } from '../interfaces'; 4 | 5 | export type ObjectKind = 'house' | 'street' | 'metro' | 'district' | 'locality'; 6 | export type Lang = 'ru_RU' | 'uk_UA' | 'be_BY' | 'en_RU' | 'en_US' | 'tr_TR'; 7 | 8 | export type YandexGeoResponse = any; 9 | 10 | export interface Address { 11 | country_code: string; 12 | formatted: string; 13 | postal_code: string; 14 | Components: {kind: string, name: string}[]; 15 | } 16 | 17 | export class Geocoder { 18 | static API_KEY = ''; 19 | 20 | static init(apiKey: string) { 21 | Geocoder.API_KEY = apiKey; 22 | } 23 | 24 | private static async requestWithQuery(query: Query) { 25 | const res = await fetch( 26 | 'https://geocode-maps.yandex.ru/1.x?' + query.toQueryString(), 27 | { 28 | method: 'get', 29 | headers: { 30 | 'content-type': 'application/json', 31 | accept: 'application/json', 32 | } 33 | } 34 | ); 35 | 36 | if (res.status !== 200) { 37 | throw new GeocodingApiError(res); 38 | } 39 | 40 | return res.json(); 41 | } 42 | 43 | private static getFirst(response: any): any { 44 | // @ts-ignore 45 | return Object.values(response.GeoObjectCollection.featureMember[0])[0]; 46 | } 47 | 48 | static async geocode(geocode: Point, kind?: ObjectKind, results?: number, skip?: number, lang?: Lang): Promise { 49 | const query = new Query({ 50 | apikey: Geocoder.API_KEY, 51 | geocode: `${geocode.lat},${geocode.lon}`, 52 | sco: 'latlong', 53 | kind, 54 | format: 'json', 55 | results, 56 | skip, 57 | lang, 58 | }); 59 | 60 | return Geocoder.requestWithQuery(query); 61 | } 62 | 63 | static reverseGeocode(geocode: string, kind?: ObjectKind, results?: number, skip?: number, lang?: Lang, rspn?: 0 | 1, ll?: Point, spn?: [number, number], bbox?: [Point, Point]): Promise { 64 | const query = new Query({ 65 | apikey: Geocoder.API_KEY, 66 | geocode, 67 | format: 'json', 68 | results, 69 | skip, 70 | lang, 71 | rspn, 72 | ll: ll ? `${ll.lat},${ll.lon}` : undefined, 73 | spn: spn ? `${spn[0]},${spn[1]}` : undefined, 74 | bbox: bbox 75 | ? `${bbox[0].lat},${bbox[0].lon}-${bbox[1].lat},${bbox[1].lon}` 76 | : undefined 77 | }); 78 | 79 | return Geocoder.requestWithQuery(query); 80 | } 81 | 82 | static async addressToGeo(address: string): Promise { 83 | const { response } = await Geocoder.reverseGeocode(address); 84 | 85 | if ( 86 | response.GeoObjectCollection 87 | && response.GeoObjectCollection.featureMember 88 | && response.GeoObjectCollection.featureMember.length > 0 89 | ) { 90 | const obj = Geocoder.getFirst(response); 91 | 92 | if (obj.Point) { 93 | const [lon, lat] = obj.Point.pos.split(' ').map(Number); 94 | return { lon, lat }; 95 | } 96 | } 97 | 98 | return undefined; 99 | } 100 | 101 | static async geoToAddress(geo: Point, lang?: Lang, kind?: ObjectKind): Promise
{ 102 | const { response } = await Geocoder.geocode(geo, kind, 1, 0, lang); 103 | 104 | if ( 105 | response.GeoObjectCollection 106 | && response.GeoObjectCollection.featureMember 107 | && response.GeoObjectCollection.featureMember.length > 0 108 | ) { 109 | const obj = Geocoder.getFirst(response); 110 | return obj.metaDataProperty.GeocoderMetaData.Address; 111 | } 112 | 113 | return undefined; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/YamapPolylineManager.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap 2 | 3 | import android.view.View 4 | import com.facebook.react.bridge.ReadableArray 5 | import com.facebook.react.common.MapBuilder 6 | import com.facebook.react.uimanager.ThemedReactContext 7 | import com.facebook.react.uimanager.ViewGroupManager 8 | import com.facebook.react.uimanager.annotations.ReactProp 9 | import com.yandex.mapkit.geometry.Point 10 | import ru.vvdev.yamap.view.YamapPolyline 11 | import javax.annotation.Nonnull 12 | 13 | class YamapPolylineManager internal constructor() : ViewGroupManager() { 14 | override fun getName(): String { 15 | return REACT_CLASS 16 | } 17 | 18 | override fun getExportedCustomDirectEventTypeConstants(): Map? { 19 | return MapBuilder.builder() 20 | .put("onPress", MapBuilder.of("registrationName", "onPress")) 21 | .build() 22 | } 23 | 24 | override fun getExportedCustomBubblingEventTypeConstants(): MutableMap? { 25 | return MapBuilder.builder() 26 | .build() 27 | } 28 | 29 | private fun castToPolylineView(view: View): YamapPolyline { 30 | return view as YamapPolyline 31 | } 32 | 33 | @Nonnull 34 | public override fun createViewInstance(@Nonnull context: ThemedReactContext): YamapPolyline { 35 | return YamapPolyline(context) 36 | } 37 | 38 | // PROPS 39 | @ReactProp(name = "points") 40 | fun setPoints(view: View, points: ReadableArray?) { 41 | if (points != null) { 42 | val parsed = ArrayList() 43 | for (i in 0 until points.size()) { 44 | val markerMap = points.getMap(i) 45 | if (markerMap != null) { 46 | val lon = markerMap.getDouble("lon") 47 | val lat = markerMap.getDouble("lat") 48 | val point = Point(lat, lon) 49 | parsed.add(point) 50 | } 51 | } 52 | castToPolylineView(view).setPolygonPoints(parsed) 53 | } 54 | } 55 | 56 | @ReactProp(name = "strokeWidth") 57 | fun setStrokeWidth(view: View, width: Float) { 58 | castToPolylineView(view).setStrokeWidth(width) 59 | } 60 | 61 | @ReactProp(name = "strokeColor") 62 | fun setStrokeColor(view: View, color: Int) { 63 | castToPolylineView(view).setStrokeColor(color) 64 | } 65 | 66 | @ReactProp(name = "zIndex") 67 | fun setZIndex(view: View, zIndex: Int) { 68 | castToPolylineView(view).setZIndex(zIndex) 69 | } 70 | 71 | @ReactProp(name = "dashLength") 72 | fun setDashLength(view: View, length: Int) { 73 | castToPolylineView(view).setDashLength(length) 74 | } 75 | 76 | @ReactProp(name = "dashOffset") 77 | fun setDashOffset(view: View, offset: Int) { 78 | castToPolylineView(view).setDashOffset(offset.toFloat()) 79 | } 80 | 81 | @ReactProp(name = "gapLength") 82 | fun setGapLength(view: View, length: Int) { 83 | castToPolylineView(view).setGapLength(length) 84 | } 85 | 86 | @ReactProp(name = "outlineWidth") 87 | fun setOutlineWidth(view: View, width: Int) { 88 | castToPolylineView(view).setOutlineWidth(width) 89 | } 90 | 91 | @ReactProp(name = "outlineColor") 92 | fun setOutlineColor(view: View, color: Int) { 93 | castToPolylineView(view).setOutlineColor(color) 94 | } 95 | 96 | @ReactProp(name = "handled") 97 | fun setHandled(view: View, handled: Boolean?) { 98 | castToPolylineView(view).setHandled(handled ?: true) 99 | } 100 | 101 | companion object { 102 | const val REACT_CLASS: String = "YamapPolyline" 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /ios/View/YamapPolylineView.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import 4 | @import YandexMapsMobile; 5 | 6 | #ifndef MAX 7 | #import 8 | #endif 9 | 10 | #import "YamapPolylineView.h" 11 | 12 | @implementation YamapPolylineView { 13 | NSMutableArray * _points; 14 | YMKPolylineMapObject* mapObject; 15 | YMKPolyline* polyline; 16 | UIColor* strokeColor; 17 | UIColor* outlineColor; 18 | NSNumber* strokeWidth; 19 | NSNumber* dashLength; 20 | NSNumber* dashOffset; 21 | NSNumber* gapLength; 22 | NSNumber* outlineWidth; 23 | NSNumber* zIndex; 24 | BOOL handled; 25 | } 26 | 27 | - (instancetype)init { 28 | self = [super init]; 29 | strokeColor = UIColor.blackColor; 30 | outlineColor = UIColor.blackColor; 31 | zIndex = [[NSNumber alloc] initWithInt:1]; 32 | strokeWidth = [[NSNumber alloc] initWithInt:1]; 33 | dashLength = [[NSNumber alloc] initWithInt:1]; 34 | gapLength = [[NSNumber alloc] initWithInt:0]; 35 | outlineWidth = [[NSNumber alloc] initWithInt:0]; 36 | dashOffset = [[NSNumber alloc] initWithInt:0]; 37 | _points = [[NSMutableArray alloc] init]; 38 | handled = YES; 39 | polyline = [YMKPolyline polylineWithPoints:_points]; 40 | 41 | return self; 42 | } 43 | 44 | - (void)updatePolyline { 45 | if (mapObject != nil) { 46 | [mapObject setGeometry:polyline]; 47 | [mapObject setZIndex:[zIndex floatValue]]; 48 | [mapObject setStrokeColorWithColor:strokeColor]; 49 | [mapObject setStrokeWidth:[strokeWidth floatValue]]; 50 | [mapObject setDashLength:[dashLength floatValue]]; 51 | [mapObject setGapLength:[gapLength floatValue]]; 52 | [mapObject setDashOffset:[dashOffset floatValue]]; 53 | [mapObject setOutlineWidth:[outlineWidth floatValue]]; 54 | [mapObject setOutlineColor:outlineColor]; 55 | } 56 | } 57 | 58 | - (void)setStrokeColor:(UIColor*)color { 59 | strokeColor = color; 60 | [self updatePolyline]; 61 | } 62 | 63 | - (void)setStrokeWidth:(NSNumber*)width { 64 | strokeWidth = width; 65 | [self updatePolyline]; 66 | } 67 | 68 | - (void)setOutlineWidth:(NSNumber*)width { 69 | outlineWidth = width; 70 | [self updatePolyline]; 71 | } 72 | 73 | - (void)setDashLength:(NSNumber*)length { 74 | dashLength = length; 75 | [self updatePolyline]; 76 | } 77 | 78 | - (void)setDashOffset:(NSNumber*)offset { 79 | dashOffset = offset; 80 | [self updatePolyline]; 81 | } 82 | 83 | - (void)setGapLength:(NSNumber*)length { 84 | gapLength = length; 85 | [self updatePolyline]; 86 | } 87 | 88 | - (void)setOutlineColor:(UIColor*)color { 89 | outlineColor = color; 90 | [self updatePolyline]; 91 | } 92 | 93 | - (void)setZIndex:(NSNumber*)_zIndex { 94 | zIndex = _zIndex; 95 | [self updatePolyline]; 96 | } 97 | 98 | - (void)setPolylinePoints:(NSMutableArray*)points { 99 | _points = points; 100 | polyline = [YMKPolyline polylineWithPoints:points]; 101 | [self updatePolyline]; 102 | } 103 | 104 | - (void)setMapObject:(YMKPolylineMapObject*)_mapObject { 105 | mapObject = _mapObject; 106 | [mapObject addTapListenerWithTapListener:self]; 107 | [self updatePolyline]; 108 | } 109 | 110 | - (void)setHandled:(BOOL)_handled { 111 | handled = _handled; 112 | } 113 | 114 | 115 | - (BOOL)onMapObjectTapWithMapObject:(nonnull YMKMapObject*)mapObject point:(nonnull YMKPoint*)point { 116 | if (self.onPress) 117 | self.onPress(@{}); 118 | 119 | return handled; 120 | } 121 | 122 | - (NSMutableArray*)getPoints { 123 | return _points; 124 | } 125 | 126 | - (YMKPolyline*)getPolyline { 127 | return polyline; 128 | } 129 | 130 | - (YMKPolylineMapObject*)getMapObject { 131 | return mapObject; 132 | } 133 | 134 | @end 135 | -------------------------------------------------------------------------------- /ios/View/YamapPolygonView.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import 4 | @import YandexMapsMobile; 5 | 6 | #ifndef MAX 7 | #import 8 | #endif 9 | 10 | #import "YamapPolygonView.h" 11 | 12 | 13 | #define ANDROID_COLOR(c) [UIColor colorWithRed:((c>>16)&0xFF)/255.0 green:((c>>8)&0xFF)/255.0 blue:((c)&0xFF)/255.0 alpha:((c>>24)&0xFF)/255.0] 14 | 15 | #define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0] 16 | 17 | @implementation YamapPolygonView { 18 | NSMutableArray* _points; 19 | NSArray*>* innerRings; 20 | YMKPolygonMapObject* mapObject; 21 | YMKPolygon* polygon; 22 | UIColor* fillColor; 23 | UIColor* strokeColor; 24 | NSNumber* strokeWidth; 25 | NSNumber* zIndex; 26 | BOOL handled; 27 | } 28 | 29 | - (instancetype)init { 30 | self = [super init]; 31 | fillColor = UIColor.blackColor; 32 | strokeColor = UIColor.blackColor; 33 | zIndex = [[NSNumber alloc] initWithInt:1]; 34 | handled = YES; 35 | strokeWidth = [[NSNumber alloc] initWithInt:1]; 36 | _points = [[NSMutableArray alloc] init]; 37 | innerRings = [[NSMutableArray alloc] init]; 38 | polygon = [YMKPolygon polygonWithOuterRing:[YMKLinearRing linearRingWithPoints:@[]] innerRings:@[]]; 39 | 40 | return self; 41 | } 42 | 43 | - (void)updatePolygon { 44 | if (mapObject != nil) { 45 | [mapObject setGeometry:polygon]; 46 | [mapObject setZIndex:[zIndex floatValue]]; 47 | [mapObject setFillColor:fillColor]; 48 | [mapObject setStrokeColor:strokeColor]; 49 | [mapObject setStrokeWidth:[strokeWidth floatValue]]; 50 | } 51 | } 52 | 53 | - (void)setFillColor:(UIColor*)color { 54 | fillColor = color; 55 | [self updatePolygon]; 56 | } 57 | 58 | - (void)setStrokeColor:(UIColor*)color { 59 | strokeColor = color; 60 | [self updatePolygon]; 61 | } 62 | 63 | - (void)setStrokeWidth:(NSNumber*)width { 64 | strokeWidth = width; 65 | [self updatePolygon]; 66 | } 67 | 68 | - (void)setZIndex:(NSNumber*)_zIndex { 69 | zIndex = _zIndex; 70 | [self updatePolygon]; 71 | } 72 | 73 | - (void)updatePolygonGeometry { 74 | YMKLinearRing* ring = [YMKLinearRing linearRingWithPoints:_points]; 75 | NSMutableArray* _innerRings = [[NSMutableArray alloc] init]; 76 | 77 | for (int i = 0; i < [innerRings count]; ++i) { 78 | YMKLinearRing* iRing = [YMKLinearRing linearRingWithPoints:[innerRings objectAtIndex:i]]; 79 | [_innerRings addObject:iRing]; 80 | } 81 | polygon = [YMKPolygon polygonWithOuterRing:ring innerRings:_innerRings]; 82 | } 83 | 84 | - (void)setPolygonPoints:(NSMutableArray*)points { 85 | _points = points; 86 | [self updatePolygonGeometry]; 87 | [self updatePolygon]; 88 | } 89 | 90 | - (void)setInnerRings:(NSArray*>*)_innerRings { 91 | innerRings = _innerRings; 92 | [self updatePolygonGeometry]; 93 | [self updatePolygon]; 94 | } 95 | 96 | - (void)setHandled:(BOOL)_handled { 97 | handled = _handled; 98 | } 99 | 100 | - (void)setMapObject:(YMKPolygonMapObject *)_mapObject { 101 | mapObject = _mapObject; 102 | [mapObject addTapListenerWithTapListener:self]; 103 | [self updatePolygon]; 104 | } 105 | 106 | - (BOOL)onMapObjectTapWithMapObject:(nonnull YMKMapObject*)mapObject point:(nonnull YMKPoint*)point { 107 | if (self.onPress) 108 | self.onPress(@{}); 109 | 110 | return handled; 111 | } 112 | 113 | - (NSMutableArray*)getPoints { 114 | return _points; 115 | } 116 | 117 | - (YMKPolygon*)getPolygon { 118 | return polygon; 119 | } 120 | 121 | - (YMKPolygonMapObject*)getMapObject { 122 | return mapObject; 123 | } 124 | 125 | @end 126 | -------------------------------------------------------------------------------- /ios/YamapMarker.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import "YamapMarker.h" 5 | #import "RNYamap.h" 6 | 7 | #import "View/YamapMarkerView.h" 8 | #import "View/RNYMView.h" 9 | 10 | #import "Converter/RCTConvert+Yamap.m" 11 | 12 | #ifndef MAX 13 | #import 14 | #endif 15 | 16 | @implementation YamapMarker 17 | 18 | RCT_EXPORT_MODULE() 19 | 20 | - (NSArray*)supportedEvents { 21 | return @[@"onPress"]; 22 | } 23 | 24 | - (instancetype)init { 25 | self = [super init]; 26 | 27 | return self; 28 | } 29 | 30 | + (BOOL)requiresMainQueueSetup { 31 | return YES; 32 | } 33 | 34 | - (UIView* _Nullable)view { 35 | return [[YamapMarkerView alloc] init]; 36 | } 37 | 38 | // PROPS 39 | RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock) 40 | 41 | RCT_CUSTOM_VIEW_PROPERTY (point, YMKPoint, YamapMarkerView) { 42 | if (json != nil) { 43 | [view setPoint: [RCTConvert YMKPoint:json]]; 44 | } 45 | } 46 | 47 | RCT_CUSTOM_VIEW_PROPERTY(scale, NSNumber, YamapMarkerView) { 48 | [view setScale: json]; 49 | } 50 | 51 | RCT_CUSTOM_VIEW_PROPERTY(rotated, NSNumber, YamapMarkerView) { 52 | [view setRotated: json]; 53 | } 54 | 55 | RCT_CUSTOM_VIEW_PROPERTY(visible, NSNumber, YamapMarkerView) { 56 | [view setVisible: json]; 57 | } 58 | 59 | RCT_CUSTOM_VIEW_PROPERTY(handled, BOOL, YamapMarkerView) { 60 | if (json == nil || [json boolValue]) { 61 | [view setHandled: YES]; 62 | } else { 63 | [view setHandled: NO]; 64 | } 65 | } 66 | 67 | RCT_CUSTOM_VIEW_PROPERTY(anchor, NSDictionary, YamapMarkerView) { 68 | CGPoint point; 69 | 70 | if (json) { 71 | CGFloat x = [[json valueForKey:@"x"] doubleValue]; 72 | CGFloat y = [[json valueForKey:@"y"] doubleValue]; 73 | point = CGPointMake(x, y); 74 | } else { 75 | point = CGPointMake(0.5, 0.5); 76 | } 77 | 78 | [view setAnchor: [NSValue valueWithCGPoint:point]]; 79 | } 80 | 81 | RCT_CUSTOM_VIEW_PROPERTY(zIndex, NSNumber, YamapMarkerView) { 82 | [view setZIndex: json]; 83 | } 84 | 85 | RCT_CUSTOM_VIEW_PROPERTY(source, NSString, YamapMarkerView) { 86 | [view setSource: json]; 87 | } 88 | 89 | // REF 90 | RCT_EXPORT_METHOD(animatedMoveTo:(nonnull NSNumber*)reactTag json:(NSDictionary*)json duration:(NSNumber*_Nonnull)duration) { 91 | @try { 92 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 93 | YamapMarkerView* view = (YamapMarkerView*)viewRegistry[reactTag]; 94 | 95 | if (!view || ![view isKindOfClass:[YamapMarkerView class]]) { 96 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 97 | return; 98 | } 99 | 100 | YMKPoint* point = [RCTConvert YMKPoint:json]; 101 | [view animatedMoveTo:point withDuration:[duration floatValue]]; 102 | }]; 103 | } @catch (NSException *exception) { 104 | NSLog(@"Reason: %@ ",exception.reason); 105 | } 106 | } 107 | 108 | RCT_EXPORT_METHOD(animatedRotateTo:(nonnull NSNumber*)reactTag angle:(NSNumber*_Nonnull)angle duration:(NSNumber*_Nonnull)duration) { 109 | @try { 110 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 111 | YamapMarkerView* view = (YamapMarkerView*)viewRegistry[reactTag]; 112 | 113 | if (!view || ![view isKindOfClass:[YamapMarkerView class]]) { 114 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 115 | return; 116 | } 117 | 118 | [view animatedRotateTo:[angle floatValue] withDuration:[duration floatValue]]; 119 | }]; 120 | } @catch (NSException *exception) { 121 | NSLog(@"Reason: %@ ",exception.reason); 122 | } 123 | } 124 | 125 | @end 126 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/YamapPolygonManager.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap 2 | 3 | import android.view.View 4 | import com.facebook.react.bridge.ReadableArray 5 | import com.facebook.react.common.MapBuilder 6 | import com.facebook.react.uimanager.ThemedReactContext 7 | import com.facebook.react.uimanager.ViewGroupManager 8 | import com.facebook.react.uimanager.annotations.ReactProp 9 | import com.yandex.mapkit.geometry.Point 10 | import ru.vvdev.yamap.view.YamapPolygon 11 | import javax.annotation.Nonnull 12 | 13 | class YamapPolygonManager internal constructor() : ViewGroupManager() { 14 | override fun getName(): String { 15 | return REACT_CLASS 16 | } 17 | 18 | override fun getExportedCustomDirectEventTypeConstants(): Map? { 19 | return MapBuilder.builder() 20 | .put("onPress", MapBuilder.of("registrationName", "onPress")) 21 | .build() 22 | } 23 | 24 | override fun getExportedCustomBubblingEventTypeConstants(): MutableMap? { 25 | return MapBuilder.builder() 26 | .build() 27 | } 28 | 29 | private fun castToPolygonView(view: View): YamapPolygon { 30 | return view as YamapPolygon 31 | } 32 | 33 | @Nonnull 34 | public override fun createViewInstance(@Nonnull context: ThemedReactContext): YamapPolygon { 35 | return YamapPolygon(context) 36 | } 37 | 38 | // PROPS 39 | @ReactProp(name = "points") 40 | fun setPoints(view: View, points: ReadableArray?) { 41 | if (points != null) { 42 | val parsed = ArrayList() 43 | for (i in 0 until points.size()) { 44 | val markerMap = points.getMap(i) 45 | if (markerMap != null) { 46 | val lon = markerMap.getDouble("lon") 47 | val lat = markerMap.getDouble("lat") 48 | val point = Point(lat, lon) 49 | parsed.add(point) 50 | } 51 | } 52 | castToPolygonView(view).setPolygonPoints(parsed) 53 | } 54 | } 55 | 56 | @ReactProp(name = "innerRings") 57 | fun setInnerRings(view: View, _rings: ReadableArray?) { 58 | val rings = ArrayList>() 59 | if (_rings != null) { 60 | for (j in 0 until _rings.size()) { 61 | val points = _rings.getArray(j) 62 | if (points != null) { 63 | val parsed = ArrayList() 64 | for (i in 0 until points.size()) { 65 | val markerMap = points.getMap(i) 66 | if (markerMap != null) { 67 | val lon = markerMap.getDouble("lon") 68 | val lat = markerMap.getDouble("lat") 69 | val point = Point(lat, lon) 70 | parsed.add(point) 71 | } 72 | } 73 | rings.add(parsed) 74 | } 75 | } 76 | } 77 | castToPolygonView(view).setPolygonInnerRings(rings) 78 | } 79 | 80 | @ReactProp(name = "strokeWidth") 81 | fun setStrokeWidth(view: View, width: Float) { 82 | castToPolygonView(view).setStrokeWidth(width) 83 | } 84 | 85 | @ReactProp(name = "strokeColor") 86 | fun setStrokeColor(view: View, color: Int) { 87 | castToPolygonView(view).setStrokeColor(color) 88 | } 89 | 90 | @ReactProp(name = "fillColor") 91 | fun setFillColor(view: View, color: Int) { 92 | castToPolygonView(view).setFillColor(color) 93 | } 94 | 95 | @ReactProp(name = "zIndex") 96 | fun setZIndex(view: View, zIndex: Int) { 97 | castToPolygonView(view).setZIndex(zIndex) 98 | } 99 | 100 | @ReactProp(name = "handled") 101 | fun setHandled(view: View, handled: Boolean?) { 102 | castToPolygonView(view).setHandled(handled ?: true) 103 | } 104 | 105 | companion object { 106 | const val REACT_CLASS: String = "YamapPolygon" 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/view/YamapPolygon.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.view 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import android.view.ViewGroup 6 | import com.facebook.react.bridge.Arguments 7 | import com.facebook.react.bridge.ReactContext 8 | import com.facebook.react.uimanager.events.RCTEventEmitter 9 | import com.yandex.mapkit.geometry.LinearRing 10 | import com.yandex.mapkit.geometry.Point 11 | import com.yandex.mapkit.geometry.Polygon 12 | import com.yandex.mapkit.map.MapObject 13 | import com.yandex.mapkit.map.MapObjectTapListener 14 | import com.yandex.mapkit.map.PolygonMapObject 15 | import ru.vvdev.yamap.models.ReactMapObject 16 | 17 | class YamapPolygon(context: Context?) : ViewGroup(context), MapObjectTapListener, ReactMapObject { 18 | @JvmField 19 | var polygon: Polygon 20 | var _points: ArrayList = ArrayList() 21 | var innerRings: ArrayList>? = ArrayList() 22 | override var rnMapObject: MapObject? = null 23 | private var fillColor = Color.BLACK 24 | private var strokeColor = Color.BLACK 25 | private var zIndex = 1 26 | private var strokeWidth = 1f 27 | private var handled = true 28 | 29 | init { 30 | polygon = Polygon(LinearRing(ArrayList()), ArrayList()) 31 | } 32 | 33 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { 34 | } 35 | 36 | // PROPS 37 | fun setPolygonPoints(points: ArrayList?) { 38 | _points = if ((points != null)) points else ArrayList() 39 | updatePolygonGeometry() 40 | updatePolygon() 41 | } 42 | 43 | fun setPolygonInnerRings(_innerRings: ArrayList>?) { 44 | innerRings = _innerRings ?: ArrayList() 45 | updatePolygonGeometry() 46 | updatePolygon() 47 | } 48 | 49 | // fun setInnerRings(_innerRings: ArrayList>?) { 50 | // innerRings = _innerRings ?: ArrayList() 51 | // updatePolygonGeometry() 52 | // updatePolygon() 53 | // } 54 | 55 | private fun updatePolygonGeometry() { 56 | val _rings = ArrayList() 57 | if (innerRings != null) { 58 | for (i in innerRings!!.indices) { 59 | _rings.add(LinearRing(innerRings!![i])) 60 | } 61 | } 62 | polygon = Polygon(LinearRing(_points), _rings) 63 | } 64 | 65 | fun setZIndex(_zIndex: Int) { 66 | zIndex = _zIndex 67 | updatePolygon() 68 | } 69 | 70 | fun setStrokeColor(_color: Int) { 71 | strokeColor = _color 72 | updatePolygon() 73 | } 74 | 75 | fun setFillColor(_color: Int) { 76 | fillColor = _color 77 | updatePolygon() 78 | } 79 | 80 | fun setStrokeWidth(width: Float) { 81 | strokeWidth = width 82 | updatePolygon() 83 | } 84 | 85 | private fun updatePolygon() { 86 | if (rnMapObject != null) { 87 | (rnMapObject as PolygonMapObject).geometry = polygon 88 | (rnMapObject as PolygonMapObject).strokeWidth = strokeWidth 89 | (rnMapObject as PolygonMapObject).strokeColor = strokeColor 90 | (rnMapObject as PolygonMapObject).fillColor = fillColor 91 | (rnMapObject as PolygonMapObject).zIndex = zIndex.toFloat() 92 | } 93 | } 94 | 95 | fun setPolygonMapObject(obj: MapObject?) { 96 | rnMapObject = obj as PolygonMapObject? 97 | rnMapObject!!.addTapListener(this) 98 | updatePolygon() 99 | } 100 | 101 | fun setHandled(_handled: Boolean) { 102 | handled = _handled 103 | } 104 | 105 | // fun setRnMapObject(obj: MapObject?) { 106 | // rnMapObject = obj as PolygonMapObject? 107 | // rnMapObject!!.addTapListener(this) 108 | // updatePolygon() 109 | // } 110 | 111 | override fun onMapObjectTap(mapObject: MapObject, point: Point): Boolean { 112 | val e = Arguments.createMap() 113 | (context as ReactContext).getJSModule(RCTEventEmitter::class.java).receiveEvent( 114 | id, "onPress", e 115 | ) 116 | 117 | return handled 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/view/YamapPolyline.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.view 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import android.view.ViewGroup 6 | import com.facebook.react.bridge.Arguments 7 | import com.facebook.react.bridge.ReactContext 8 | import com.facebook.react.uimanager.events.RCTEventEmitter 9 | import com.yandex.mapkit.geometry.Point 10 | import com.yandex.mapkit.geometry.Polyline 11 | import com.yandex.mapkit.map.MapObject 12 | import com.yandex.mapkit.map.MapObjectTapListener 13 | import com.yandex.mapkit.map.PolylineMapObject 14 | import ru.vvdev.yamap.models.ReactMapObject 15 | 16 | class YamapPolyline(context: Context?) : ViewGroup(context), MapObjectTapListener, ReactMapObject { 17 | @JvmField 18 | var polyline: Polyline 19 | var _points: ArrayList = ArrayList() 20 | override var rnMapObject: MapObject? = null 21 | private var outlineColor = Color.BLACK 22 | private var strokeColor = Color.BLACK 23 | private var zIndex = 1 24 | private var strokeWidth = 1f 25 | private var dashLength = 1 26 | private var gapLength = 0 27 | private var dashOffset = 0f 28 | private var outlineWidth = 0 29 | private var handled = true 30 | 31 | init { 32 | polyline = Polyline(ArrayList()) 33 | } 34 | 35 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { 36 | } 37 | 38 | // PROPS 39 | fun setPolygonPoints(points: ArrayList?) { 40 | _points = points ?: ArrayList() 41 | polyline = Polyline(_points) 42 | updatePolyline() 43 | } 44 | 45 | fun setZIndex(_zIndex: Int) { 46 | zIndex = _zIndex 47 | updatePolyline() 48 | } 49 | 50 | fun setStrokeColor(_color: Int) { 51 | strokeColor = _color 52 | updatePolyline() 53 | } 54 | 55 | fun setDashLength(length: Int) { 56 | dashLength = length 57 | updatePolyline() 58 | } 59 | 60 | fun setDashOffset(offset: Float) { 61 | dashOffset = offset 62 | updatePolyline() 63 | } 64 | 65 | fun setGapLength(length: Int) { 66 | gapLength = length 67 | updatePolyline() 68 | } 69 | 70 | fun setOutlineWidth(width: Int) { 71 | outlineWidth = width 72 | updatePolyline() 73 | } 74 | 75 | fun setOutlineColor(color: Int) { 76 | outlineColor = color 77 | updatePolyline() 78 | } 79 | 80 | fun setStrokeWidth(width: Float) { 81 | strokeWidth = width 82 | updatePolyline() 83 | } 84 | 85 | private fun updatePolyline() { 86 | if (rnMapObject != null) { 87 | (rnMapObject as PolylineMapObject).geometry = polyline 88 | (rnMapObject as PolylineMapObject).strokeWidth = strokeWidth 89 | (rnMapObject as PolylineMapObject).setStrokeColor(strokeColor) 90 | (rnMapObject as PolylineMapObject).zIndex = zIndex.toFloat() 91 | (rnMapObject as PolylineMapObject).dashLength = dashLength.toFloat() 92 | (rnMapObject as PolylineMapObject).gapLength = gapLength.toFloat() 93 | (rnMapObject as PolylineMapObject).dashOffset = dashOffset 94 | (rnMapObject as PolylineMapObject).outlineColor = outlineColor 95 | (rnMapObject as PolylineMapObject).outlineWidth = outlineWidth.toFloat() 96 | } 97 | } 98 | 99 | fun setPolylineMapObject(obj: MapObject?) { 100 | rnMapObject = obj as PolylineMapObject? 101 | rnMapObject!!.addTapListener(this) 102 | updatePolyline() 103 | } 104 | 105 | fun setHandled(_handled: Boolean) { 106 | handled = _handled 107 | } 108 | // fun setRnMapObject(obj: MapObject?) { 109 | // rnMapObject = obj as PolylineMapObject? 110 | // rnMapObject!!.addTapListener(this) 111 | // updatePolyline() 112 | // } 113 | 114 | override fun onMapObjectTap(mapObject: MapObject, point: Point): Boolean { 115 | val e = Arguments.createMap() 116 | (context as ReactContext).getJSModule(RCTEventEmitter::class.java).receiveEvent( 117 | id, "onPress", e 118 | ) 119 | 120 | return handled 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/YamapMarkerManager.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap 2 | 3 | import android.graphics.PointF 4 | import android.view.View 5 | import com.facebook.react.bridge.ReadableArray 6 | import com.facebook.react.bridge.ReadableMap 7 | import com.facebook.react.common.MapBuilder 8 | import com.facebook.react.uimanager.ThemedReactContext 9 | import com.facebook.react.uimanager.ViewGroupManager 10 | import com.facebook.react.uimanager.annotations.ReactProp 11 | import com.yandex.mapkit.geometry.Point 12 | import ru.vvdev.yamap.view.YamapMarker 13 | import javax.annotation.Nonnull 14 | 15 | class YamapMarkerManager internal constructor() : ViewGroupManager() { 16 | override fun getName(): String { 17 | return REACT_CLASS 18 | } 19 | 20 | override fun getExportedCustomDirectEventTypeConstants(): Map? { 21 | return MapBuilder.builder() 22 | .put("onPress", MapBuilder.of("registrationName", "onPress")) 23 | .build() 24 | } 25 | 26 | override fun getExportedCustomBubblingEventTypeConstants(): MutableMap? { 27 | return MapBuilder.builder() 28 | .build() 29 | } 30 | 31 | private fun castToMarkerView(view: View): YamapMarker { 32 | return view as YamapMarker 33 | } 34 | 35 | @Nonnull 36 | public override fun createViewInstance(@Nonnull context: ThemedReactContext): YamapMarker { 37 | return YamapMarker(context) 38 | } 39 | 40 | // PROPS 41 | @ReactProp(name = "point") 42 | fun setPoint(view: View, markerPoint: ReadableMap?) { 43 | if (markerPoint != null) { 44 | val lon = markerPoint.getDouble("lon") 45 | val lat = markerPoint.getDouble("lat") 46 | val point = Point(lat, lon) 47 | castToMarkerView(view).setPoint(point) 48 | } 49 | } 50 | 51 | @ReactProp(name = "zIndex") 52 | fun setZIndex(view: View, zIndex: Int) { 53 | castToMarkerView(view).setZIndex(zIndex) 54 | } 55 | 56 | @ReactProp(name = "scale") 57 | fun setScale(view: View, scale: Float) { 58 | castToMarkerView(view).setScale(scale) 59 | } 60 | 61 | @ReactProp(name = "handled") 62 | fun setHandled(view: View, handled: Boolean?) { 63 | castToMarkerView(view).setHandled(handled ?: true) 64 | } 65 | 66 | @ReactProp(name = "rotated") 67 | fun setRotated(view: View, rotated: Boolean?) { 68 | castToMarkerView(view).setRotated(rotated ?: true) 69 | } 70 | 71 | @ReactProp(name = "visible") 72 | fun setVisible(view: View, visible: Boolean?) { 73 | castToMarkerView(view).setVisible(visible ?: true) 74 | } 75 | 76 | @ReactProp(name = "source") 77 | fun setSource(view: View, source: String?) { 78 | if (source != null) { 79 | castToMarkerView(view).setIconSource(source) 80 | } 81 | } 82 | 83 | @ReactProp(name = "anchor") 84 | fun setAnchor(view: View, anchor: ReadableMap?) { 85 | castToMarkerView(view).setAnchor( 86 | if (anchor != null) PointF( 87 | anchor.getDouble("x").toFloat(), 88 | anchor.getDouble("y").toFloat() 89 | ) else null 90 | ) 91 | } 92 | 93 | override fun addView(parent: YamapMarker, child: View, index: Int) { 94 | parent.addChildView(child, index) 95 | super.addView(parent, child, index) 96 | } 97 | 98 | override fun removeViewAt(parent: YamapMarker, index: Int) { 99 | parent.removeChildView(index) 100 | super.removeViewAt(parent, index) 101 | } 102 | 103 | override fun receiveCommand( 104 | view: YamapMarker, 105 | commandType: String, 106 | args: ReadableArray? 107 | ) { 108 | when (commandType) { 109 | "animatedMoveTo" -> { 110 | val markerPoint = args!!.getMap(0)!! 111 | val moveDuration = args.getInt(1) 112 | val lon = markerPoint.getDouble("lon").toFloat() 113 | val lat = markerPoint.getDouble("lat").toFloat() 114 | val point = Point(lat.toDouble(), lon.toDouble()) 115 | castToMarkerView(view).animatedMoveTo(point, moveDuration.toFloat()) 116 | return 117 | } 118 | 119 | "animatedRotateTo" -> { 120 | val angle = args!!.getInt(0) 121 | val rotateDuration = args.getInt(1) 122 | castToMarkerView(view).animatedRotateTo(angle.toFloat(), rotateDuration.toFloat()) 123 | return 124 | } 125 | 126 | else -> throw IllegalArgumentException( 127 | String.format( 128 | "Unsupported command %d received by %s.", 129 | commandType, 130 | javaClass.simpleName 131 | ) 132 | ) 133 | } 134 | } 135 | 136 | companion object { 137 | const val REACT_CLASS: String = "YamapMarker" 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/search/YandexMapSearchClient.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.search 2 | 3 | import android.content.Context 4 | import com.yandex.mapkit.geometry.Geometry 5 | import com.yandex.mapkit.geometry.Point 6 | import com.yandex.mapkit.search.BusinessObjectMetadata 7 | import com.yandex.mapkit.search.Response 8 | import com.yandex.mapkit.search.SearchFactory 9 | import com.yandex.mapkit.search.SearchManager 10 | import com.yandex.mapkit.search.SearchManagerType 11 | import com.yandex.mapkit.search.SearchOptions 12 | import com.yandex.mapkit.search.Session.SearchListener 13 | import com.yandex.mapkit.search.ToponymObjectMetadata 14 | import com.yandex.mapkit.uri.UriObjectMetadata 15 | import com.yandex.runtime.Error 16 | import ru.vvdev.yamap.utils.Callback 17 | 18 | class YandexMapSearchClient(context: Context) : MapSearchClient { 19 | private val searchManager: SearchManager = SearchFactory.getInstance().createSearchManager(SearchManagerType.COMBINED) 20 | 21 | private fun transformResponse(searchResponse: Response, options: SearchOptions): MapSearchItem { 22 | val result = MapSearchItem() 23 | if (options.searchTypes==2) { 24 | result.formatted = 25 | searchResponse.collection.children.firstOrNull()?.obj?.metadataContainer 26 | ?.getItem(BusinessObjectMetadata::class.java)?.address?.formattedAddress 27 | } else { 28 | result.formatted = 29 | searchResponse.collection.children.firstOrNull()?.obj?.metadataContainer 30 | ?.getItem(ToponymObjectMetadata::class.java)?.address?.formattedAddress 31 | } 32 | result.uri = searchResponse.collection.children.firstOrNull()?.obj 33 | ?.metadataContainer 34 | ?.getItem(UriObjectMetadata::class.java) 35 | ?.uris 36 | ?.firstOrNull() 37 | ?.value 38 | result.country_code = searchResponse.collection.children.firstOrNull()?.obj?.metadataContainer 39 | ?.getItem(ToponymObjectMetadata::class.java)?.address?.countryCode 40 | result.point = searchResponse.collection.children.firstOrNull()?.obj?.geometry?.firstOrNull()?.point 41 | result.Components = ArrayList(searchResponse.collection.children.size); 42 | for (i in searchResponse.collection.children) { 43 | result.Components!!.add(MapSearchItemComponent().apply { 44 | name = i.obj?.name 45 | if (options.searchTypes==2) { 46 | kind = i.obj?.metadataContainer 47 | ?.getItem(BusinessObjectMetadata::class.java)?.address?.components?.lastOrNull()?.kinds?.firstOrNull()?.name 48 | } else { 49 | kind = i.obj?.metadataContainer 50 | ?.getItem(ToponymObjectMetadata::class.java)?.address?.components?.lastOrNull()?.kinds?.firstOrNull()?.name 51 | } 52 | }) 53 | } 54 | return result; 55 | } 56 | 57 | override fun searchPoint( 58 | point: Point, 59 | zoom: Int, 60 | options: SearchOptions, 61 | onSuccess: Callback, 62 | onError: Callback? 63 | ) { 64 | this.searchManager.submit(point, zoom, options, object: SearchListener { 65 | override fun onSearchResponse(searchResponse: Response) { 66 | onSuccess.invoke(transformResponse(searchResponse, options)) 67 | } 68 | 69 | override fun onSearchError(error: Error) { 70 | onError!!.invoke(IllegalStateException("search error: $error")) 71 | } 72 | }) 73 | } 74 | 75 | override fun searchAddress( 76 | text: String, 77 | geometry: Geometry, 78 | options: SearchOptions, 79 | onSuccess: Callback, 80 | onError: Callback? 81 | ) { 82 | this.searchManager.submit(text, geometry, options, object: SearchListener { 83 | override fun onSearchResponse(searchResponse: Response) { 84 | onSuccess.invoke(transformResponse(searchResponse, options)) 85 | } 86 | 87 | override fun onSearchError(error: Error) { 88 | onError!!.invoke(IllegalStateException("search error: $error")) 89 | } 90 | }) 91 | } 92 | 93 | override fun resolveURI( 94 | uri: String, 95 | options: SearchOptions, 96 | onSuccess: Callback, 97 | onError: Callback? 98 | ) { 99 | this.searchManager.resolveURI(uri, options, object: SearchListener { 100 | override fun onSearchResponse(searchResponse: Response) { 101 | onSuccess.invoke(transformResponse(searchResponse, options)) 102 | } 103 | 104 | override fun onSearchError(error: Error) { 105 | onError!!.invoke(IllegalStateException("search error: $error")) 106 | } 107 | }) 108 | } 109 | 110 | override fun searchByURI( 111 | uri: String, 112 | options: SearchOptions, 113 | onSuccess: Callback, 114 | onError: Callback? 115 | ) { 116 | this.searchManager.searchByURI(uri, options, object: SearchListener { 117 | override fun onSearchResponse(searchResponse: Response) { 118 | onSuccess.invoke(transformResponse(searchResponse, options)) 119 | } 120 | 121 | override fun onSearchError(error: Error) { 122 | onError!!.invoke(IllegalStateException("search error: $error")) 123 | } 124 | }) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/view/ClusteredYamapView.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.view 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.Canvas 6 | import android.graphics.Color 7 | import android.graphics.Paint 8 | import android.view.View 9 | import com.yandex.mapkit.geometry.Point 10 | import com.yandex.mapkit.map.Cluster 11 | import com.yandex.mapkit.map.ClusterListener 12 | import com.yandex.mapkit.map.ClusterTapListener 13 | import com.yandex.mapkit.map.IconStyle 14 | import com.yandex.mapkit.map.PlacemarkMapObject 15 | import com.yandex.runtime.image.ImageProvider 16 | import kotlin.math.abs 17 | import kotlin.math.sqrt 18 | 19 | class ClusteredYamapView(context: Context?) : YamapView(context), ClusterListener, 20 | ClusterTapListener { 21 | private val clusterCollection = mapWindow.map.mapObjects.addClusterizedPlacemarkCollection(this) 22 | private var clusterColor = 0 23 | private val placemarksMap: HashMap = HashMap() 24 | private var pointsList = ArrayList() 25 | 26 | fun setClusteredMarkers(points: ArrayList) { 27 | clusterCollection.clear() 28 | placemarksMap.clear() 29 | val pt = ArrayList() 30 | for (i in points.indices) { 31 | val point = points[i] as HashMap 32 | pt.add(Point(point["lat"]!!, point["lon"]!!)) 33 | } 34 | val placemarks = clusterCollection.addPlacemarks(pt, TextImageProvider(""), IconStyle()) 35 | pointsList = pt 36 | for (i in placemarks.indices) { 37 | val placemark = placemarks[i] 38 | placemarksMap["" + placemark.geometry.latitude + placemark.geometry.longitude] = 39 | placemark 40 | val child: Any? = getChildAt(i) 41 | if (child != null && child is YamapMarker) { 42 | child.setMarkerMapObject(placemark) 43 | } 44 | } 45 | clusterCollection.clusterPlacemarks(50.0, 12) 46 | } 47 | 48 | fun setClustersColor(color: Int) { 49 | clusterColor = color 50 | updateUserMarkersColor() 51 | } 52 | 53 | private fun updateUserMarkersColor() { 54 | clusterCollection.clear() 55 | val placemarks = clusterCollection.addPlacemarks( 56 | pointsList, 57 | TextImageProvider(pointsList.size.toString()), 58 | IconStyle() 59 | ) 60 | for (i in placemarks.indices) { 61 | val placemark = placemarks[i] 62 | placemarksMap["" + placemark.geometry.latitude + placemark.geometry.longitude] = 63 | placemark 64 | val child: Any? = getChildAt(i) 65 | if (child != null && child is YamapMarker) { 66 | child.setMarkerMapObject(placemark) 67 | } 68 | } 69 | clusterCollection.clusterPlacemarks(50.0, 12) 70 | } 71 | 72 | override fun addFeature(child: View?, index: Int) { 73 | val marker = child as YamapMarker? 74 | val placemark = placemarksMap["" + marker!!.point!!.latitude + marker.point!!.longitude] 75 | if (placemark != null) { 76 | marker.setMarkerMapObject(placemark) 77 | } 78 | } 79 | 80 | override fun removeChild(index: Int) { 81 | if (getChildAt(index) is YamapMarker) { 82 | val child = getChildAt(index) as YamapMarker ?: return 83 | val mapObject = child.rnMapObject 84 | if (mapObject == null || !mapObject.isValid) return 85 | clusterCollection.remove(mapObject) 86 | placemarksMap.remove("" + child.point!!.latitude + child.point!!.longitude) 87 | } 88 | } 89 | 90 | override fun onClusterAdded(cluster: Cluster) { 91 | cluster.appearance.setIcon(TextImageProvider(cluster.size.toString())) 92 | cluster.addClusterTapListener(this) 93 | } 94 | 95 | override fun onClusterTap(cluster: Cluster): Boolean { 96 | val points = ArrayList() 97 | for (placemark in cluster.placemarks) { 98 | points.add(placemark.geometry) 99 | } 100 | fitMarkers(points) 101 | return true 102 | } 103 | 104 | private inner class TextImageProvider(private val text: String) : ImageProvider() { 105 | override fun getId(): String { 106 | return "text_$text" 107 | } 108 | 109 | override fun getImage(): Bitmap { 110 | val textPaint = Paint() 111 | textPaint.textSize = Companion.FONT_SIZE 112 | textPaint.textAlign = Paint.Align.CENTER 113 | textPaint.style = Paint.Style.FILL 114 | textPaint.isAntiAlias = true 115 | 116 | val widthF = textPaint.measureText(text) 117 | val textMetrics = textPaint.fontMetrics 118 | val heightF = 119 | (abs(textMetrics.bottom.toDouble()) + abs(textMetrics.top.toDouble())).toFloat() 120 | val textRadius = sqrt((widthF * widthF + heightF * heightF).toDouble()) 121 | .toFloat() / 2 122 | val internalRadius = textRadius + Companion.MARGIN_SIZE 123 | val externalRadius = internalRadius + Companion.STROKE_SIZE 124 | 125 | val width = (2 * externalRadius + 0.5).toInt() 126 | 127 | val bitmap = Bitmap.createBitmap(width, width, Bitmap.Config.ARGB_8888) 128 | val canvas = Canvas(bitmap) 129 | 130 | val backgroundPaint = Paint() 131 | backgroundPaint.isAntiAlias = true 132 | backgroundPaint.color = clusterColor 133 | canvas.drawCircle( 134 | (width / 2).toFloat(), 135 | (width / 2).toFloat(), 136 | externalRadius, 137 | backgroundPaint 138 | ) 139 | 140 | backgroundPaint.color = Color.WHITE 141 | canvas.drawCircle( 142 | (width / 2).toFloat(), 143 | (width / 2).toFloat(), 144 | internalRadius, 145 | backgroundPaint 146 | ) 147 | 148 | canvas.drawText( 149 | text, 150 | (width / 2).toFloat(), 151 | width / 2 - (textMetrics.ascent + textMetrics.descent) / 2, 152 | textPaint 153 | ) 154 | 155 | return bitmap 156 | } 157 | } 158 | companion object { 159 | private const val FONT_SIZE = 45f 160 | private const val MARGIN_SIZE = 9f 161 | private const val STROKE_SIZE = 9f 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /ios/YamapSuggests.swift: -------------------------------------------------------------------------------- 1 | import YandexMapsMobile 2 | 3 | @objc(YamapSuggests) 4 | class YamapSuggests: NSObject { 5 | var searchManager: YMKSearchManager? 6 | var suggestClient: YMKSearchSuggestSession? 7 | let defaultBoundingBox: YMKBoundingBox 8 | let suggestOptions: YMKSuggestOptions 9 | 10 | override init() { 11 | let southWestPoint = YMKPoint(latitude: -90.0, longitude: -180.0) 12 | let northEastPoint = YMKPoint(latitude: 90.0, longitude: 180.0) 13 | self.defaultBoundingBox = YMKBoundingBox(southWest: southWestPoint, northEast: northEastPoint) 14 | self.suggestOptions = YMKSuggestOptions(suggestTypes: [], userPosition: nil, suggestWords: false, strictBounds: false) 15 | super.init() 16 | } 17 | 18 | @objc static func requiresMainQueueSetup() -> Bool { 19 | return true 20 | } 21 | 22 | func runOnMainQueueWithoutDeadlocking(_ block: @escaping () -> Void) { 23 | if Thread.isMainThread { 24 | block() 25 | } else { 26 | DispatchQueue.main.sync(execute: block) 27 | } 28 | } 29 | 30 | func runAsyncOnMainQueueWithoutDeadlocking(_ block: @escaping () -> Void) { 31 | if Thread.isMainThread { 32 | block() 33 | } else { 34 | DispatchQueue.main.async(execute: block) 35 | } 36 | } 37 | 38 | let ERR_NO_REQUEST_ARG = "YANDEX_SUGGEST_ERR_NO_REQUEST_ARG" 39 | let ERR_SUGGEST_FAILED = "YANDEX_SUGGEST_ERR_SUGGEST_FAILED" 40 | let YandexSuggestErrorDomain = "YandexSuggestErrorDomain" 41 | 42 | func getSuggestClient() -> YMKSearchSuggestSession { 43 | if let client = suggestClient { 44 | return client 45 | } 46 | 47 | if searchManager == nil { 48 | runOnMainQueueWithoutDeadlocking { 49 | self.searchManager = YMKSearchFactory.instance().createSearchManager(with: .online) 50 | } 51 | } 52 | 53 | runOnMainQueueWithoutDeadlocking { 54 | self.suggestClient = self.searchManager?.createSuggestSession() 55 | } 56 | 57 | return suggestClient! 58 | } 59 | 60 | @objc func suggestHandler(_ searchQuery: String, options: YMKSuggestOptions, boundingBox: YMKBoundingBox, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) { 61 | do { 62 | let session = getSuggestClient() 63 | 64 | runAsyncOnMainQueueWithoutDeadlocking { 65 | session.suggest(withText: searchQuery, window: boundingBox, suggestOptions: options) { suggest, error in 66 | if let error = error { 67 | rejecter(self.ERR_SUGGEST_FAILED, "search request: \(searchQuery)", error) 68 | return 69 | } 70 | 71 | var suggestsToPass = [[String: Any]]() 72 | 73 | suggest?.items.forEach { suggestItem in 74 | var suggestToPass = [String: Any]() 75 | suggestToPass["title"] = suggestItem.title.text 76 | suggestToPass["subtitle"] = suggestItem.subtitle?.text 77 | suggestToPass["uri"] = suggestItem.uri 78 | suggestsToPass.append(suggestToPass) 79 | } 80 | 81 | resolver(suggestsToPass) 82 | } 83 | } 84 | } catch { 85 | rejecter(ERR_NO_REQUEST_ARG, "search request: \(searchQuery)", nil) 86 | } 87 | } 88 | 89 | func makeError(withText descriptionText: String) -> NSError { 90 | let errorDictionary = [NSLocalizedDescriptionKey: descriptionText] 91 | return NSError(domain: YandexSuggestErrorDomain, code: 0, userInfo: errorDictionary) 92 | } 93 | 94 | func mapPoint(fromDictionary: [String: Any], withKey pointKey: String) throws -> YMKPoint? { 95 | guard let pointDictionary = fromDictionary[pointKey] as? [String: Any], 96 | let lat = pointDictionary["lat"] as? NSNumber, 97 | let lon = pointDictionary["lon"] as? NSNumber else { 98 | throw makeError(withText: "search request: \(pointKey) is not an Object") 99 | } 100 | 101 | return YMKPoint(latitude: lat.doubleValue, longitude: lon.doubleValue) 102 | } 103 | 104 | @objc func suggest(_ searchQuery: String, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) { 105 | suggestHandler(searchQuery, options: self.suggestOptions, boundingBox: self.defaultBoundingBox, resolver: resolver, rejecter: rejecter) 106 | } 107 | 108 | @objc func suggestWithOptions(_ searchQuery: String, options: [String: Any], resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) { 109 | var suggestType: YMKSuggestType = .geo 110 | var boundingBox = self.defaultBoundingBox 111 | let opt = YMKSuggestOptions() 112 | 113 | if let suggestWords = options["suggestWords"] as? Bool { 114 | opt.suggestWords = suggestWords 115 | } else if options["suggestWords"] != nil { 116 | rejecter(ERR_NO_REQUEST_ARG, "search request: suggestWords must be a Boolean", nil) 117 | return 118 | } 119 | 120 | if let suggestTypes = options["suggestTypes"] as? [NSNumber] { 121 | suggestType = [] 122 | for value in suggestTypes { 123 | suggestType.insert(YMKSuggestType(rawValue: value.uintValue)) 124 | } 125 | } else if options["suggestTypes"] != nil { 126 | rejecter(ERR_NO_REQUEST_ARG, "search request: suggestTypes is not an Array", nil) 127 | return 128 | } 129 | 130 | opt.suggestTypes = suggestType 131 | 132 | if let userPosition = options["userPosition"] as? [String: Any] { 133 | do { 134 | if let userPoint = try mapPoint(fromDictionary: options, withKey: "userPosition") { 135 | opt.userPosition = userPoint 136 | } 137 | } catch { 138 | rejecter(ERR_NO_REQUEST_ARG, error.localizedDescription, nil) 139 | return 140 | } 141 | } 142 | 143 | if let boxDictionary = options["boundingBox"] as? [String: Any] { 144 | do { 145 | if let southWest = try mapPoint(fromDictionary: boxDictionary, withKey: "southWest"), 146 | let northEast = try mapPoint(fromDictionary: boxDictionary, withKey: "northEast") { 147 | boundingBox = YMKBoundingBox(southWest: southWest, northEast: northEast) 148 | } 149 | } catch { 150 | rejecter(ERR_NO_REQUEST_ARG, error.localizedDescription, nil) 151 | return 152 | } 153 | } else if options["boundingBox"] != nil { 154 | rejecter(ERR_NO_REQUEST_ARG, "search request: boundingBox is not an Object", nil) 155 | return 156 | } 157 | 158 | suggestHandler(searchQuery, options: opt, boundingBox: boundingBox, resolver: resolver, rejecter: rejecter) 159 | } 160 | 161 | @objc func reset(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) { 162 | do { 163 | if let client = suggestClient { 164 | DispatchQueue.main.async { 165 | client.reset() 166 | } 167 | } 168 | resolve([]) 169 | } catch { 170 | reject("ERROR", "Error during reset suggestions", nil) 171 | } 172 | } 173 | 174 | @objc static func moduleName() -> String { 175 | return "YamapSuggests" 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/view/YamapMarker.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.view 2 | 3 | import android.animation.ValueAnimator 4 | import android.content.Context 5 | import android.graphics.Bitmap 6 | import android.graphics.Canvas 7 | import android.graphics.PointF 8 | import android.view.View 9 | import android.view.View.OnLayoutChangeListener 10 | import android.view.animation.LinearInterpolator 11 | import com.facebook.react.bridge.Arguments 12 | import com.facebook.react.bridge.ReactContext 13 | import com.facebook.react.uimanager.events.RCTEventEmitter 14 | import com.facebook.react.views.view.ReactViewGroup 15 | import com.yandex.mapkit.geometry.Point 16 | import com.yandex.mapkit.map.IconStyle 17 | import com.yandex.mapkit.map.MapObject 18 | import com.yandex.mapkit.map.MapObjectTapListener 19 | import com.yandex.mapkit.map.PlacemarkMapObject 20 | import com.yandex.mapkit.map.RotationType 21 | import com.yandex.runtime.image.ImageProvider 22 | import ru.vvdev.yamap.models.ReactMapObject 23 | import ru.vvdev.yamap.utils.Callback 24 | import ru.vvdev.yamap.utils.ImageLoader.DownloadImageBitmap 25 | 26 | class YamapMarker(context: Context?) : ReactViewGroup(context), MapObjectTapListener, 27 | ReactMapObject { 28 | @JvmField 29 | var point: Point? = null 30 | private var zIndex = 1 31 | private var scale = 1f 32 | private var visible = true 33 | private var handled = true 34 | private var rotated = false 35 | private val YAMAP_FRAMES_PER_SECOND = 25 36 | private var markerAnchor: PointF? = null 37 | private var iconSource: String? = null 38 | private var _childView: View? = null 39 | override var rnMapObject: MapObject? = null 40 | private val childs = ArrayList() 41 | 42 | private val childLayoutListener = 43 | OnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> updateMarker() } 44 | 45 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { 46 | } 47 | 48 | // PROPS 49 | fun setPoint(_point: Point?) { 50 | point = _point 51 | updateMarker() 52 | } 53 | 54 | fun setZIndex(_zIndex: Int) { 55 | zIndex = _zIndex 56 | updateMarker() 57 | } 58 | 59 | fun setScale(_scale: Float) { 60 | scale = _scale 61 | updateMarker() 62 | } 63 | 64 | fun setHandled(_handled: Boolean) { 65 | handled = _handled 66 | } 67 | 68 | fun setRotated(_rotated: Boolean) { 69 | rotated = _rotated 70 | updateMarker() 71 | } 72 | 73 | fun setVisible(_visible: Boolean) { 74 | visible = _visible 75 | updateMarker() 76 | } 77 | 78 | fun setIconSource(source: String?) { 79 | iconSource = source 80 | updateMarker() 81 | } 82 | 83 | fun setAnchor(anchor: PointF?) { 84 | markerAnchor = anchor 85 | updateMarker() 86 | } 87 | 88 | private fun updateMarker() { 89 | if (rnMapObject != null && rnMapObject!!.isValid) { 90 | val iconStyle = IconStyle() 91 | iconStyle.setScale(scale) 92 | iconStyle.setRotationType(if (rotated) RotationType.ROTATE else RotationType.NO_ROTATION) 93 | iconStyle.setVisible(visible) 94 | if (markerAnchor != null) { 95 | iconStyle.setAnchor(markerAnchor) 96 | } 97 | (rnMapObject as PlacemarkMapObject).geometry = point!! 98 | (rnMapObject as PlacemarkMapObject).zIndex = zIndex.toFloat() 99 | (rnMapObject as PlacemarkMapObject).setIconStyle(iconStyle) 100 | 101 | if (_childView != null) { 102 | try { 103 | val b = Bitmap.createBitmap( 104 | _childView!!.width, _childView!!.height, Bitmap.Config.ARGB_8888 105 | ) 106 | val c = Canvas(b) 107 | _childView!!.draw(c) 108 | (rnMapObject as PlacemarkMapObject).setIcon(ImageProvider.fromBitmap(b)) 109 | (rnMapObject as PlacemarkMapObject).setIconStyle(iconStyle) 110 | } catch (e: Exception) { 111 | e.printStackTrace() 112 | } 113 | } 114 | if (childs.size == 0) { 115 | if (iconSource != "") { 116 | iconSource?.let { 117 | DownloadImageBitmap(context, it, object : Callback { 118 | override fun invoke(arg: Bitmap?) { 119 | try { 120 | val icon = ImageProvider.fromBitmap(arg) 121 | (rnMapObject as PlacemarkMapObject).setIcon(icon) 122 | (rnMapObject as PlacemarkMapObject).setIconStyle(iconStyle) 123 | } catch (e: Exception) { 124 | e.printStackTrace() 125 | } 126 | } 127 | }) 128 | } 129 | } 130 | } 131 | } 132 | } 133 | 134 | fun setMarkerMapObject(obj: MapObject?) { 135 | rnMapObject = obj as PlacemarkMapObject? 136 | rnMapObject!!.addTapListener(this) 137 | updateMarker() 138 | } 139 | 140 | fun setChildView(view: View?) { 141 | if (view == null) { 142 | _childView!!.removeOnLayoutChangeListener(childLayoutListener) 143 | _childView = null 144 | updateMarker() 145 | return 146 | } 147 | _childView = view 148 | _childView!!.addOnLayoutChangeListener(childLayoutListener) 149 | } 150 | 151 | fun addChildView(view: View, index: Int) { 152 | childs.add(index, view) 153 | setChildView(childs[0]) 154 | } 155 | 156 | fun removeChildView(index: Int) { 157 | childs.removeAt(index) 158 | setChildView(if (childs.size > 0) childs[0] else null) 159 | } 160 | 161 | fun moveAnimationLoop(lat: Double, lon: Double) { 162 | (rnMapObject as PlacemarkMapObject).geometry = Point(lat, lon) 163 | } 164 | 165 | fun rotateAnimationLoop(delta: Float) { 166 | (rnMapObject as PlacemarkMapObject).direction = delta 167 | } 168 | 169 | fun animatedMoveTo(point: Point, duration: Float) { 170 | val p = (rnMapObject as PlacemarkMapObject).geometry 171 | val startLat = p.latitude 172 | val startLon = p.longitude 173 | val deltaLat = point.latitude - startLat 174 | val deltaLon = point.longitude - startLon 175 | val valueAnimator = ValueAnimator.ofFloat(0f, 1f) 176 | valueAnimator.setDuration(duration.toLong()) 177 | valueAnimator.interpolator = LinearInterpolator() 178 | valueAnimator.addUpdateListener { animation -> 179 | try { 180 | val v = animation.animatedFraction 181 | moveAnimationLoop(startLat + v * deltaLat, startLon + v * deltaLon) 182 | } catch (ex: Exception) { 183 | // I don't care atm.. 184 | } 185 | } 186 | valueAnimator.start() 187 | } 188 | 189 | fun animatedRotateTo(angle: Float, duration: Float) { 190 | val placemark = (rnMapObject as PlacemarkMapObject) 191 | val startDirection = placemark.direction 192 | val delta = angle - placemark.direction 193 | val valueAnimator = ValueAnimator.ofFloat(0f, 1f) 194 | valueAnimator.setDuration(duration.toLong()) 195 | valueAnimator.interpolator = LinearInterpolator() 196 | valueAnimator.addUpdateListener { animation -> 197 | try { 198 | val v = animation.animatedFraction 199 | rotateAnimationLoop(startDirection + v * delta) 200 | } catch (ex: Exception) { 201 | // I don't care atm.. 202 | } 203 | } 204 | valueAnimator.start() 205 | } 206 | 207 | override fun onMapObjectTap(mapObject: MapObject, point: Point): Boolean { 208 | val e = Arguments.createMap() 209 | (context as ReactContext).getJSModule(RCTEventEmitter::class.java).receiveEvent( 210 | id, "onPress", e 211 | ) 212 | 213 | return handled 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /android/src/main/java/ru/vvdev/yamap/suggest/YandexMapSuggestClient.kt: -------------------------------------------------------------------------------- 1 | package ru.vvdev.yamap.suggest 2 | 3 | import android.content.Context 4 | import com.facebook.react.bridge.ReadableMap 5 | import com.facebook.react.bridge.ReadableType 6 | import com.yandex.mapkit.geometry.BoundingBox 7 | import com.yandex.mapkit.geometry.Point 8 | import com.yandex.mapkit.search.SearchFactory 9 | import com.yandex.mapkit.search.SearchManager 10 | import com.yandex.mapkit.search.SearchManagerType 11 | import com.yandex.mapkit.search.SearchType 12 | import com.yandex.mapkit.search.SuggestOptions 13 | import com.yandex.mapkit.search.SuggestResponse 14 | import com.yandex.mapkit.search.SuggestSession 15 | import com.yandex.mapkit.search.SuggestSession.SuggestListener 16 | import com.yandex.mapkit.search.SuggestType 17 | import com.yandex.runtime.Error 18 | import ru.vvdev.yamap.utils.Callback 19 | 20 | class YandexMapSuggestClient(context: Context?) : MapSuggestClient { 21 | private val searchManager: SearchManager 22 | private val suggestOptions = SuggestOptions() 23 | private var suggestSession: SuggestSession? = null 24 | 25 | /** 26 | * Для Яндекса нужно указать географическую область поиска. В дефолтном варианте мы не знаем какие 27 | * границы для каждого конкретного города, поэтому поиск осуществляется по всему миру. 28 | * Для `BoundingBox` нужно указать ширину и долготу для юго-западной точки и северо-восточной 29 | * в градусах. Получается, что координаты самой юго-западной точки, это 30 | * ширина = -90, долгота = -180, а самой северо-восточной - ширина = 90, долгота = 180 31 | */ 32 | private val defaultGeometry = BoundingBox(Point(-90.0, -180.0), Point(90.0, 180.0)) 33 | 34 | init { 35 | searchManager = SearchFactory.getInstance().createSearchManager(SearchManagerType.COMBINED) 36 | suggestOptions.setSuggestTypes(SearchType.GEO.value) 37 | } 38 | 39 | private fun suggestHandler( 40 | text: String?, 41 | options: SuggestOptions, 42 | boundingBox: BoundingBox, 43 | onSuccess: Callback?>?, 44 | onError: Callback? 45 | ) { 46 | if (suggestSession == null) { 47 | suggestSession = searchManager.createSuggestSession() 48 | } 49 | 50 | suggestSession!!.suggest( 51 | text!!, 52 | boundingBox, 53 | options, 54 | object : SuggestListener { 55 | override fun onResponse(suggestResponse: SuggestResponse) { 56 | val result: MutableList = ArrayList(suggestResponse.items.size) 57 | for (i in suggestResponse.items.indices) { 58 | val rawSuggest = suggestResponse.items[i] 59 | val suggest = MapSuggestItem() 60 | suggest.searchText = rawSuggest.searchText 61 | suggest.title = rawSuggest.title.text 62 | if (rawSuggest.subtitle != null) { 63 | suggest.subtitle = rawSuggest.subtitle!!.text 64 | } 65 | suggest.uri = rawSuggest.uri 66 | result.add(suggest) 67 | } 68 | onSuccess!!.invoke(result) 69 | } 70 | 71 | override fun onError(error: Error) { 72 | onError!!.invoke(IllegalStateException("suggest error: $error")) 73 | } 74 | } 75 | ) 76 | } 77 | 78 | private fun mapPoint(readableMap: ReadableMap?, pointKey: String): Point { 79 | val lonKey = "lon" 80 | val latKey = "lat" 81 | 82 | check(readableMap!!.getType(pointKey) == ReadableType.Map) { "suggest error: $pointKey is not an Object" } 83 | val pointMap = readableMap.getMap(pointKey) 84 | 85 | check(!(!pointMap!!.hasKey(latKey) || !pointMap.hasKey(lonKey))) { "suggest error: $pointKey does not have lat or lon" } 86 | 87 | check(!(pointMap.getType(latKey) != ReadableType.Number || pointMap.getType(lonKey) != ReadableType.Number)) { "suggest error: lat or lon is not a Number" } 88 | 89 | val lat = pointMap.getDouble(latKey) 90 | val lon = pointMap.getDouble(lonKey) 91 | 92 | return Point(lat, lon) 93 | } 94 | 95 | override fun suggest( 96 | text: String?, 97 | onSuccess: Callback?>?, 98 | onError: Callback? 99 | ) { 100 | this.suggestHandler(text, this.suggestOptions, this.defaultGeometry, onSuccess, onError) 101 | } 102 | 103 | override fun suggest( 104 | text: String?, 105 | options: ReadableMap?, 106 | onSuccess: Callback?>?, 107 | onError: Callback? 108 | ) { 109 | val userPositionKey = "userPosition" 110 | val suggestWordsKey = "suggestWords" 111 | val suggestTypesKey = "suggestTypes" 112 | val boundingBoxKey = "boundingBox" 113 | val southWestKey = "southWest" 114 | val northEastKey = "northEast" 115 | 116 | val options_ = SuggestOptions() 117 | 118 | var suggestType = SuggestType.GEO.value 119 | var boundingBox = this.defaultGeometry 120 | 121 | if (options!!.hasKey(suggestWordsKey) && !options.isNull(suggestWordsKey)) { 122 | if (options.getType(suggestWordsKey) != ReadableType.Boolean) { 123 | onError!!.invoke(IllegalStateException("suggest error: $suggestWordsKey is not a Boolean")) 124 | return 125 | } 126 | val suggestWords = options.getBoolean(suggestWordsKey) 127 | 128 | options_.setSuggestWords(suggestWords) 129 | } 130 | 131 | if (options.hasKey(boundingBoxKey) && !options.isNull(boundingBoxKey)) { 132 | if (options.getType(boundingBoxKey) != ReadableType.Map) { 133 | onError!!.invoke(IllegalStateException("suggest error: $boundingBoxKey is not an Object")) 134 | return 135 | } 136 | val boundingBoxMap = options.getMap(boundingBoxKey) 137 | 138 | if (!boundingBoxMap!!.hasKey(southWestKey) || !boundingBoxMap.hasKey(northEastKey)) { 139 | onError!!.invoke(IllegalStateException("suggest error: $boundingBoxKey does not have southWest or northEast")) 140 | return 141 | } 142 | 143 | try { 144 | val southWest = mapPoint(boundingBoxMap, southWestKey) 145 | val northEast = mapPoint(boundingBoxMap, northEastKey) 146 | boundingBox = BoundingBox(southWest, northEast) 147 | } catch (bbex: Exception) { 148 | onError!!.invoke(bbex) 149 | return 150 | } 151 | } 152 | 153 | if (options.hasKey(userPositionKey) && !options.isNull(userPositionKey)) { 154 | try { 155 | val userPosition = mapPoint(options, userPositionKey) 156 | options_.setUserPosition(userPosition) 157 | } catch (upex: Exception) { 158 | onError!!.invoke(upex) 159 | return 160 | } 161 | } 162 | 163 | if (options.hasKey(suggestTypesKey) && !options.isNull(suggestTypesKey)) { 164 | if (options.getType(suggestTypesKey) != ReadableType.Array) { 165 | onError!!.invoke(IllegalStateException("suggest error: $suggestTypesKey is not an Array")) 166 | return 167 | } 168 | suggestType = SuggestType.UNSPECIFIED.value 169 | val suggestTypesArray = options.getArray(suggestTypesKey) 170 | for (i in 0 until suggestTypesArray!!.size()) { 171 | if (suggestTypesArray.getType(i) != ReadableType.Number) { 172 | onError!!.invoke(IllegalStateException("suggest error: one or more $suggestTypesKey is not an Number")) 173 | return 174 | } 175 | val value = suggestTypesArray.getInt(i) 176 | suggestType = suggestType or value 177 | } 178 | } 179 | 180 | options_.setSuggestTypes(suggestType) 181 | this.suggestHandler(text, options_, boundingBox, onSuccess, onError) 182 | } 183 | 184 | override fun resetSuggest() { 185 | if (suggestSession != null) { 186 | suggestSession!!.reset() 187 | suggestSession = null 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /ios/View/YamapMarkerView.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | @import YandexMapsMobile; 6 | 7 | #ifndef MAX 8 | #import 9 | #endif 10 | 11 | #import "YamapMarkerView.h" 12 | 13 | #define ANDROID_COLOR(c) [UIColor colorWithRed:((c>>16)&0xFF)/255.0 green:((c>>8)&0xFF)/255.0 blue:((c)&0xFF)/255.0 alpha:((c>>24)&0xFF)/255.0] 14 | 15 | #define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0] 16 | 17 | #define YAMAP_FRAMES_PER_SECOND 25 18 | 19 | @implementation YamapMarkerView { 20 | YMKPoint* _point; 21 | YMKPlacemarkMapObject* mapObject; 22 | NSNumber* zIndex; 23 | NSNumber* scale; 24 | NSNumber* rotated; 25 | NSString* source; 26 | NSString* lastSource; 27 | NSValue* anchor; 28 | NSNumber* visible; 29 | BOOL handled; 30 | NSMutableArray* _reactSubviews; 31 | UIView* _childView; 32 | } 33 | 34 | - (instancetype)init { 35 | self = [super init]; 36 | zIndex = [[NSNumber alloc] initWithInt:1]; 37 | scale = [[NSNumber alloc] initWithInt:1]; 38 | rotated = [[NSNumber alloc] initWithInt:0]; 39 | visible = [[NSNumber alloc] initWithInt:1]; 40 | handled = YES; 41 | _reactSubviews = [[NSMutableArray alloc] init]; 42 | source = @""; 43 | lastSource = @""; 44 | 45 | return self; 46 | } 47 | 48 | - (void)updateMarker { 49 | if (mapObject != nil && mapObject.valid) { 50 | [mapObject setGeometry:_point]; 51 | [mapObject setZIndex:[zIndex floatValue]]; 52 | YMKIconStyle* iconStyle = [[YMKIconStyle alloc] init]; 53 | [iconStyle setScale:scale]; 54 | [iconStyle setVisible:visible]; 55 | if (anchor) { 56 | [iconStyle setAnchor:anchor]; 57 | } 58 | [iconStyle setRotationType:rotated]; 59 | if ([_reactSubviews count] == 0) { 60 | if (![source isEqual:@""]) { 61 | if (![source isEqual:lastSource]) { 62 | [mapObject setIconWithImage:[self resolveUIImage:source]]; 63 | lastSource = source; 64 | } 65 | } 66 | } 67 | [mapObject setIconStyleWithStyle:iconStyle]; 68 | } 69 | } 70 | 71 | 72 | - (void)updateClusterMarker { 73 | if (mapObject != nil && mapObject.valid) { 74 | [mapObject setGeometry:_point]; 75 | [mapObject setZIndex:[zIndex floatValue]]; 76 | YMKIconStyle* iconStyle = [[YMKIconStyle alloc] init]; 77 | [iconStyle setScale:scale]; 78 | [iconStyle setVisible:visible]; 79 | if (anchor) { 80 | [iconStyle setAnchor:anchor]; 81 | } 82 | [iconStyle setRotationType:rotated]; 83 | if ([_reactSubviews count] == 0) { 84 | if (![source isEqualToString:@""] && source != nil) { 85 | UIImage *image = [self resolveUIImage:source]; 86 | if (image) { 87 | [mapObject setIconWithImage:image]; 88 | lastSource = source; 89 | } 90 | } 91 | } 92 | [mapObject setIconStyleWithStyle:iconStyle]; 93 | } 94 | } 95 | 96 | - (void)setScale:(NSNumber*)_scale { 97 | scale = _scale; 98 | [self updateMarker]; 99 | } 100 | - (void)setRotated:(NSNumber*) _rotated { 101 | rotated = _rotated; 102 | [self updateMarker]; 103 | } 104 | 105 | - (void)setZIndex:(NSNumber*)_zIndex { 106 | zIndex = _zIndex; 107 | [self updateMarker]; 108 | } 109 | 110 | - (void)setVisible:(NSNumber*)_visible { 111 | visible = _visible; 112 | [self updateMarker]; 113 | } 114 | 115 | - (void)setHandled:(BOOL)_handled { 116 | handled = _handled; 117 | } 118 | 119 | - (void)setPoint:(YMKPoint*)point { 120 | _point = point; 121 | [self updateMarker]; 122 | } 123 | 124 | - (UIImage *)resolveUIImage:(NSString *)uri { 125 | UIImage *icon = nil; 126 | 127 | if (!uri || [uri isEqualToString:@""]) { 128 | NSLog(@"URI is nil or empty"); 129 | return nil; 130 | } 131 | 132 | NSURL *url = [NSURL URLWithString:uri]; 133 | if (!url) { 134 | NSLog(@"Failed to create URL from URI: %@", uri); 135 | return nil; 136 | } 137 | 138 | NSData *imageData = [NSData dataWithContentsOfURL:url]; 139 | if (!imageData) { 140 | NSLog(@"Failed to load image data from URL: %@", uri); 141 | return nil; 142 | } 143 | 144 | icon = [UIImage imageWithData:imageData]; 145 | if (!icon) { 146 | NSLog(@"Failed to create image from loaded data: %@", uri); 147 | return nil; 148 | } 149 | 150 | return icon; 151 | } 152 | 153 | - (void)setSource:(NSString*)_source { 154 | source = _source; 155 | [self updateMarker]; 156 | } 157 | 158 | - (void)setMapObject:(YMKPlacemarkMapObject *)_mapObject { 159 | mapObject = _mapObject; 160 | [mapObject addTapListenerWithTapListener:self]; 161 | [self updateMarker]; 162 | } 163 | 164 | - (void)setClusterMapObject:(YMKPlacemarkMapObject *)_mapObject { 165 | mapObject = _mapObject; 166 | [mapObject addTapListenerWithTapListener:self]; 167 | [self updateClusterMarker]; 168 | } 169 | 170 | // object tap listener 171 | - (BOOL)onMapObjectTapWithMapObject:(nonnull YMKMapObject*)_mapObject point:(nonnull YMKPoint*)point { 172 | if (self.onPress) 173 | self.onPress(@{}); 174 | 175 | return handled; 176 | } 177 | 178 | - (YMKPoint*)getPoint { 179 | return _point; 180 | } 181 | 182 | - (void)setAnchor:(NSValue*)_anchor { 183 | anchor = _anchor; 184 | } 185 | 186 | - (YMKPlacemarkMapObject*)getMapObject { 187 | return mapObject; 188 | } 189 | 190 | - (void)setChildView { 191 | if ([_reactSubviews count] > 0) { 192 | _childView = [_reactSubviews objectAtIndex:0]; 193 | if (_childView != nil) { 194 | [_childView setOpaque:false]; 195 | YRTViewProvider* v = [[YRTViewProvider alloc] initWithUIView:_childView]; 196 | if (v != nil) { 197 | if (mapObject.isValid) { 198 | [mapObject setViewWithView:v]; 199 | [self updateMarker]; 200 | } 201 | } 202 | } 203 | } else { 204 | _childView = nil; 205 | } 206 | } 207 | 208 | - (void)didUpdateReactSubviews { 209 | dispatch_async(dispatch_get_main_queue(), ^{ 210 | [self setChildView]; 211 | }); 212 | } 213 | 214 | - (void)insertReactSubview:(UIView*)subview atIndex:(NSInteger)atIndex { 215 | [_reactSubviews insertObject:subview atIndex: atIndex]; 216 | [super insertReactSubview:subview atIndex:atIndex]; 217 | } 218 | 219 | - (void)removeReactSubview:(UIView*)subview { 220 | [_reactSubviews removeObject:subview]; 221 | [super removeReactSubview: subview]; 222 | } 223 | 224 | - (void)moveAnimationLoop:(NSInteger)frame withTotalFrames:(NSInteger)totalFrames withDeltaLat:(double)deltaLat withDeltaLon:(double)deltaLon { 225 | @try { 226 | YMKPlacemarkMapObject *placemark = [self getMapObject]; 227 | YMKPoint* p = placemark.geometry; 228 | placemark.geometry = [YMKPoint pointWithLatitude:p.latitude + deltaLat/totalFrames 229 | longitude:p.longitude + deltaLon/totalFrames]; 230 | 231 | if (frame < totalFrames) { 232 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / YAMAP_FRAMES_PER_SECOND), dispatch_get_main_queue(), ^{ 233 | [self moveAnimationLoop: frame+1 withTotalFrames:totalFrames withDeltaLat:deltaLat withDeltaLon:deltaLon]; 234 | }); 235 | } 236 | } @catch (NSException *exception) { 237 | NSLog(@"Reason: %@ ",exception.reason); 238 | } 239 | } 240 | 241 | - (void)rotateAnimationLoop:(NSInteger)frame withTotalFrames:(NSInteger)totalFrames withDelta:(double)delta { 242 | @try { 243 | YMKPlacemarkMapObject *placemark = [self getMapObject]; 244 | [placemark setDirection:placemark.direction+(delta / totalFrames)]; 245 | 246 | if (frame < totalFrames) { 247 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / YAMAP_FRAMES_PER_SECOND), dispatch_get_main_queue(), ^{ 248 | [self rotateAnimationLoop: frame+1 withTotalFrames:totalFrames withDelta:delta]; 249 | }); 250 | } 251 | } @catch (NSException *exception) { 252 | NSLog(@"Reason: %@ ",exception.reason); 253 | } 254 | } 255 | 256 | - (void)animatedMoveTo:(YMKPoint*)point withDuration:(float)duration { 257 | @try { 258 | YMKPlacemarkMapObject* placemark = [self getMapObject]; 259 | YMKPoint* p = placemark.geometry; 260 | double deltaLat = point.latitude - p.latitude; 261 | double deltaLon = point.longitude - p.longitude; 262 | [self moveAnimationLoop: 0 withTotalFrames:[@(duration / YAMAP_FRAMES_PER_SECOND) integerValue] withDeltaLat:deltaLat withDeltaLon:deltaLon]; 263 | } @catch (NSException *exception) { 264 | NSLog(@"Reason: %@ ",exception.reason); 265 | } 266 | } 267 | 268 | - (void)animatedRotateTo:(float)angle withDuration:(float)duration { 269 | @try { 270 | YMKPlacemarkMapObject* placemark = [self getMapObject]; 271 | double delta = angle - placemark.direction; 272 | [self rotateAnimationLoop: 0 withTotalFrames:[@(duration / YAMAP_FRAMES_PER_SECOND) integerValue] withDelta:delta]; 273 | } @catch (NSException *exception) { 274 | NSLog(@"Reason: %@ ",exception.reason); 275 | } 276 | } 277 | 278 | @synthesize reactTag; 279 | 280 | @end 281 | -------------------------------------------------------------------------------- /src/components/Yamap.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Platform, 4 | requireNativeComponent, 5 | NativeModules, 6 | UIManager, 7 | findNodeHandle, 8 | ViewProps, 9 | ImageSourcePropType, 10 | NativeSyntheticEvent 11 | } from 'react-native'; 12 | // @ts-ignore 13 | import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; 14 | import CallbacksManager from '../utils/CallbacksManager'; 15 | import { 16 | Point, 17 | ScreenPoint, 18 | DrivingInfo, 19 | MasstransitInfo, 20 | RoutesFoundEvent, 21 | Vehicles, 22 | CameraPosition, 23 | VisibleRegion, 24 | InitialRegion, 25 | Animation, 26 | MapLoaded, 27 | YandexLogoPosition, 28 | YandexLogoPadding 29 | } from '../interfaces'; 30 | import { processColorProps } from '../utils'; 31 | 32 | const { yamap: NativeYamapModule } = NativeModules; 33 | 34 | export interface YaMapProps extends ViewProps { 35 | userLocationIcon?: ImageSourcePropType; 36 | userLocationIconScale?: number; 37 | showUserPosition?: boolean; 38 | nightMode?: boolean; 39 | mapStyle?: string; 40 | onCameraPositionChange?: (event: NativeSyntheticEvent) => void; 41 | onCameraPositionChangeEnd?: (event: NativeSyntheticEvent) => void; 42 | onMapPress?: (event: NativeSyntheticEvent) => void; 43 | onMapLongPress?: (event: NativeSyntheticEvent) => void; 44 | onMapLoaded?: (event: NativeSyntheticEvent) => void; 45 | userLocationAccuracyFillColor?: string; 46 | userLocationAccuracyStrokeColor?: string; 47 | userLocationAccuracyStrokeWidth?: number; 48 | scrollGesturesEnabled?: boolean; 49 | zoomGesturesEnabled?: boolean; 50 | tiltGesturesEnabled?: boolean; 51 | rotateGesturesEnabled?: boolean; 52 | fastTapEnabled?: boolean; 53 | initialRegion?: InitialRegion; 54 | maxFps?: number; 55 | followUser?: boolean; 56 | logoPosition?: YandexLogoPosition; 57 | logoPadding?: YandexLogoPadding; 58 | } 59 | 60 | const YaMapNativeComponent = requireNativeComponent('YamapView'); 61 | 62 | export class YaMap extends React.Component { 63 | static defaultProps = { 64 | showUserPosition: true, 65 | clusterColor: 'red', 66 | maxFps: 60 67 | }; 68 | 69 | // @ts-ignore 70 | map = React.createRef(); 71 | 72 | static ALL_MASSTRANSIT_VEHICLES: Vehicles[] = [ 73 | 'bus', 74 | 'trolleybus', 75 | 'tramway', 76 | 'minibus', 77 | 'suburban', 78 | 'underground', 79 | 'ferry', 80 | 'cable', 81 | 'funicular', 82 | ]; 83 | 84 | public static init(apiKey: string): Promise { 85 | return NativeYamapModule.init(apiKey); 86 | } 87 | 88 | public static setLocale(locale: string): Promise { 89 | return new Promise((resolve, reject) => { 90 | NativeYamapModule.setLocale(locale, () => resolve(), (err: string) => reject(new Error(err))); 91 | }); 92 | } 93 | 94 | public static getLocale(): Promise { 95 | return new Promise((resolve, reject) => { 96 | NativeYamapModule.getLocale((locale: string) => resolve(locale), (err: string) => reject(new Error(err))); 97 | }); 98 | } 99 | 100 | public static resetLocale(): Promise { 101 | return new Promise((resolve, reject) => { 102 | NativeYamapModule.resetLocale(() => resolve(), (err: string) => reject(new Error(err))); 103 | }); 104 | } 105 | 106 | public findRoutes(points: Point[], vehicles: Vehicles[], callback: (event: RoutesFoundEvent) => void) { 107 | this._findRoutes(points, vehicles, callback); 108 | } 109 | 110 | public findMasstransitRoutes(points: Point[], callback: (event: RoutesFoundEvent) => void) { 111 | this._findRoutes(points, YaMap.ALL_MASSTRANSIT_VEHICLES, callback); 112 | } 113 | 114 | public findPedestrianRoutes(points: Point[], callback: (event: RoutesFoundEvent) => void) { 115 | this._findRoutes(points, [], callback); 116 | } 117 | 118 | public findDrivingRoutes(points: Point[], callback: (event: RoutesFoundEvent) => void) { 119 | this._findRoutes(points, ['car'], callback); 120 | } 121 | 122 | public fitAllMarkers() { 123 | UIManager.dispatchViewManagerCommand( 124 | findNodeHandle(this), 125 | this.getCommand('fitAllMarkers'), 126 | [] 127 | ); 128 | } 129 | 130 | public setTrafficVisible(isVisible: boolean) { 131 | UIManager.dispatchViewManagerCommand( 132 | findNodeHandle(this), 133 | this.getCommand('setTrafficVisible'), 134 | [isVisible] 135 | ); 136 | } 137 | 138 | public fitMarkers(points: Point[]) { 139 | UIManager.dispatchViewManagerCommand( 140 | findNodeHandle(this), 141 | this.getCommand('fitMarkers'), 142 | [points] 143 | ); 144 | } 145 | 146 | public setCenter(center: { lon: number, lat: number, zoom?: number }, zoom: number = center.zoom || 10, azimuth: number = 0, tilt: number = 0, duration: number = 0, animation: Animation = Animation.SMOOTH) { 147 | UIManager.dispatchViewManagerCommand( 148 | findNodeHandle(this), 149 | this.getCommand('setCenter'), 150 | [center, zoom, azimuth, tilt, duration, animation] 151 | ); 152 | } 153 | 154 | public setZoom(zoom: number, duration: number = 0, animation: Animation = Animation.SMOOTH) { 155 | UIManager.dispatchViewManagerCommand( 156 | findNodeHandle(this), 157 | this.getCommand('setZoom'), 158 | [zoom, duration, animation] 159 | ); 160 | } 161 | 162 | public getCameraPosition(callback: (position: CameraPosition) => void) { 163 | const cbId = CallbacksManager.addCallback(callback); 164 | UIManager.dispatchViewManagerCommand( 165 | findNodeHandle(this), 166 | this.getCommand('getCameraPosition'), 167 | [cbId] 168 | ); 169 | } 170 | 171 | public getVisibleRegion(callback: (VisibleRegion: VisibleRegion) => void) { 172 | const cbId = CallbacksManager.addCallback(callback); 173 | UIManager.dispatchViewManagerCommand( 174 | findNodeHandle(this), 175 | this.getCommand('getVisibleRegion'), 176 | [cbId] 177 | ); 178 | } 179 | 180 | public getScreenPoints(points: Point[], callback: (screenPoint: ScreenPoint) => void) { 181 | const cbId = CallbacksManager.addCallback(callback); 182 | UIManager.dispatchViewManagerCommand( 183 | findNodeHandle(this), 184 | this.getCommand('getScreenPoints'), 185 | [points, cbId] 186 | ); 187 | } 188 | 189 | public getWorldPoints(points: ScreenPoint[], callback: (point: Point) => void) { 190 | const cbId = CallbacksManager.addCallback(callback); 191 | UIManager.dispatchViewManagerCommand( 192 | findNodeHandle(this), 193 | this.getCommand('getWorldPoints'), 194 | [points, cbId] 195 | ); 196 | } 197 | 198 | private _findRoutes(points: Point[], vehicles: Vehicles[], callback: ((event: RoutesFoundEvent) => void) | ((event: RoutesFoundEvent) => void) | ((event: RoutesFoundEvent) => void)) { 199 | const cbId = CallbacksManager.addCallback(callback); 200 | const args = Platform.OS === 'ios' ? [{ points, vehicles, id: cbId }] : [points, vehicles, cbId]; 201 | 202 | UIManager.dispatchViewManagerCommand( 203 | findNodeHandle(this), 204 | this.getCommand('findRoutes'), 205 | args 206 | ); 207 | } 208 | 209 | private getCommand(cmd: string): any { 210 | return Platform.OS === 'ios' ? UIManager.getViewManagerConfig('YamapView').Commands[cmd] : cmd; 211 | } 212 | 213 | private processRoute(event: NativeSyntheticEvent<{ id: string } & RoutesFoundEvent>) { 214 | const { id, ...routes } = event.nativeEvent; 215 | CallbacksManager.call(id, routes); 216 | } 217 | 218 | private processCameraPosition(event: NativeSyntheticEvent<{ id: string } & CameraPosition>) { 219 | const { id, ...point } = event.nativeEvent; 220 | CallbacksManager.call(id, point); 221 | } 222 | 223 | private processVisibleRegion(event: NativeSyntheticEvent<{ id: string } & VisibleRegion>) { 224 | const { id, ...visibleRegion } = event.nativeEvent; 225 | CallbacksManager.call(id, visibleRegion); 226 | } 227 | 228 | private processWorldToScreenPointsReceived(event: NativeSyntheticEvent<{ id: string } & ScreenPoint[]>) { 229 | const { id, ...screenPoints } = event.nativeEvent; 230 | CallbacksManager.call(id, screenPoints); 231 | } 232 | 233 | private processScreenToWorldPointsReceived(event: NativeSyntheticEvent<{ id: string } & Point[]>) { 234 | const { id, ...worldPoints } = event.nativeEvent; 235 | CallbacksManager.call(id, worldPoints); 236 | } 237 | 238 | private resolveImageUri(img: ImageSourcePropType) { 239 | return img ? resolveAssetSource(img).uri : ''; 240 | } 241 | 242 | private getProps() { 243 | const props = { 244 | ...this.props, 245 | onRouteFound: this.processRoute, 246 | onCameraPositionReceived: this.processCameraPosition, 247 | onVisibleRegionReceived: this.processVisibleRegion, 248 | onWorldToScreenPointsReceived: this.processWorldToScreenPointsReceived, 249 | onScreenToWorldPointsReceived: this.processScreenToWorldPointsReceived, 250 | userLocationIcon: this.props.userLocationIcon ? this.resolveImageUri(this.props.userLocationIcon) : undefined 251 | }; 252 | 253 | processColorProps(props, 'clusterColor' as keyof YaMapProps); 254 | processColorProps(props, 'userLocationAccuracyFillColor' as keyof YaMapProps); 255 | processColorProps(props, 'userLocationAccuracyStrokeColor' as keyof YaMapProps); 256 | 257 | return props; 258 | } 259 | 260 | render() { 261 | return ( 262 | 266 | ); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /ios/View/RNCYMView.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import "../Converter/RCTConvert+Yamap.m" 6 | @import YandexMapsMobile; 7 | 8 | #ifndef MAX 9 | #import 10 | #endif 11 | 12 | #import "RNCYMView.h" 13 | #import 14 | 15 | #define ANDROID_COLOR(c) [UIColor colorWithRed:((c>>16)&0xFF)/255.0 green:((c>>8)&0xFF)/255.0 blue:((c)&0xFF)/255.0 alpha:((c>>24)&0xFF)/255.0] 16 | 17 | #define UIColorFromRGB(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0] 18 | 19 | @implementation RNCYMView { 20 | YMKMasstransitSession *masstransitSession; 21 | YMKMasstransitSession *walkSession; 22 | YMKMasstransitRouter *masstransitRouter; 23 | YMKDrivingRouter* drivingRouter; 24 | YMKDrivingSession* drivingSession; 25 | YMKPedestrianRouter *pedestrianRouter; 26 | YMKTransitOptions *transitOptions; 27 | YMKMasstransitSessionRouteHandler routeHandler; 28 | NSMutableArray* _reactSubviews; 29 | NSMutableArray *routes; 30 | NSMutableArray *currentRouteInfo; 31 | NSMutableArray* lastKnownRoutePoints; 32 | YMKUserLocationView* userLocationView; 33 | NSMutableDictionary *vehicleColors; 34 | UIImage* userLocationImage; 35 | NSArray *acceptVehicleTypes; 36 | YMKUserLocationLayer *userLayer; 37 | UIColor* userLocationAccuracyFillColor; 38 | UIColor* userLocationAccuracyStrokeColor; 39 | float userLocationAccuracyStrokeWidth; 40 | YMKClusterizedPlacemarkCollection *clusterCollection; 41 | UIColor* clusterColor; 42 | NSMutableArray* placemarks; 43 | BOOL userClusters; 44 | Boolean initializedRegion; 45 | } 46 | 47 | - (instancetype)init { 48 | self = [super init]; 49 | _reactSubviews = [[NSMutableArray alloc] init]; 50 | placemarks = [[NSMutableArray alloc] init]; 51 | clusterColor=nil; 52 | userClusters=NO; 53 | clusterCollection = [self.mapWindow.map.mapObjects addClusterizedPlacemarkCollectionWithClusterListener:self]; 54 | initializedRegion = NO; 55 | return self; 56 | } 57 | 58 | - (void)setClusteredMarkers:(NSArray*) markers { 59 | [placemarks removeAllObjects]; 60 | [clusterCollection clear]; 61 | NSMutableArray *newMarkers = [NSMutableArray new]; 62 | for (NSDictionary *mark in markers) { 63 | [newMarkers addObject:[YMKPoint pointWithLatitude:[[mark objectForKey:@"lat"] doubleValue] longitude:[[mark objectForKey:@"lon"] doubleValue]]]; 64 | } 65 | NSArray* newPlacemarks = [clusterCollection addPlacemarksWithPoints:newMarkers image:[self clusterImage:[NSNumber numberWithFloat:[newMarkers count]]] style:[YMKIconStyle new]]; 66 | [placemarks addObjectsFromArray:newPlacemarks]; 67 | for (int i=0; i<[placemarks count]; i++) { 68 | if (i<[_reactSubviews count]) { 69 | UIView *subview = [_reactSubviews objectAtIndex:i]; 70 | if ([subview isKindOfClass:[YamapMarkerView class]]) { 71 | YamapMarkerView* marker = (YamapMarkerView*) subview; 72 | [marker setClusterMapObject:[placemarks objectAtIndex:i]]; 73 | } 74 | } 75 | } 76 | [clusterCollection clusterPlacemarksWithClusterRadius:50 minZoom:12]; 77 | } 78 | 79 | - (void)setClusterColor: (UIColor*) color { 80 | clusterColor = color; 81 | } 82 | 83 | - (void)onObjectRemovedWithView:(nonnull YMKUserLocationView *) view { 84 | } 85 | 86 | - (void)onMapTapWithMap:(nonnull YMKMap *) map 87 | point:(nonnull YMKPoint *) point { 88 | if (self.onMapPress) { 89 | NSDictionary* data = @{ 90 | @"lat": [NSNumber numberWithDouble:point.latitude], 91 | @"lon": [NSNumber numberWithDouble:point.longitude], 92 | }; 93 | self.onMapPress(data); 94 | } 95 | } 96 | 97 | - (void)onMapLongTapWithMap:(nonnull YMKMap *) map 98 | point:(nonnull YMKPoint *) point { 99 | if (self.onMapLongPress) { 100 | NSDictionary* data = @{ 101 | @"lat": [NSNumber numberWithDouble:point.latitude], 102 | @"lon": [NSNumber numberWithDouble:point.longitude], 103 | }; 104 | self.onMapLongPress(data); 105 | } 106 | } 107 | 108 | // utils 109 | + (UIColor*)colorFromHexString:(NSString*) hexString { 110 | unsigned rgbValue = 0; 111 | NSScanner *scanner = [NSScanner scannerWithString:hexString]; 112 | [scanner setScanLocation:1]; 113 | [scanner scanHexInt:&rgbValue]; 114 | return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16)/255.0 green:((rgbValue & 0xFF00) >> 8)/255.0 blue:(rgbValue & 0xFF)/255.0 alpha:1.0]; 115 | } 116 | 117 | + (NSString*)hexStringFromColor:(UIColor *) color { 118 | const CGFloat *components = CGColorGetComponents(color.CGColor); 119 | CGFloat r = components[0]; 120 | CGFloat g = components[1]; 121 | CGFloat b = components[2]; 122 | return [NSString stringWithFormat:@"#%02lX%02lX%02lX", lroundf(r * 255), lroundf(g * 255), lroundf(b * 255)]; 123 | } 124 | 125 | // children 126 | - (void)addSubview:(UIView *) view { 127 | [super addSubview:view]; 128 | } 129 | 130 | - (void)insertReactSubview:(UIView*) subview atIndex:(NSInteger) atIndex { 131 | if ([subview isKindOfClass:[YamapMarkerView class]]) { 132 | YamapMarkerView* marker = (YamapMarkerView*) subview; 133 | if (atIndex<[placemarks count]) { 134 | [marker setClusterMapObject:[placemarks objectAtIndex:atIndex]]; 135 | } 136 | } 137 | [_reactSubviews insertObject:subview atIndex:atIndex]; 138 | [super insertMarkerReactSubview:subview atIndex:atIndex]; 139 | } 140 | 141 | - (void)removeReactSubview:(UIView*) subview { 142 | if ([subview isKindOfClass:[YamapMarkerView class]]) { 143 | YamapMarkerView* marker = (YamapMarkerView*) subview; 144 | [clusterCollection removeWithMapObject:[marker getMapObject]]; 145 | } else { 146 | NSArray> *childSubviews = [subview reactSubviews]; 147 | for (int i = 0; i < childSubviews.count; i++) { 148 | [self removeReactSubview:(UIView *)childSubviews[i]]; 149 | } 150 | } 151 | [_reactSubviews removeObject:subview]; 152 | [super removeMarkerReactSubview:subview]; 153 | } 154 | 155 | -(UIImage*)clusterImage:(NSNumber*) clusterSize { 156 | float FONT_SIZE = 45; 157 | float MARGIN_SIZE = 9; 158 | float STROKE_SIZE = 9; 159 | NSString *text = [clusterSize stringValue]; 160 | UIFont *font = [UIFont systemFontOfSize:FONT_SIZE]; 161 | CGSize size = [text sizeWithFont:font]; 162 | float textRadius = sqrt(size.height * size.height + size.width * size.width) / 2; 163 | float internalRadius = textRadius + MARGIN_SIZE; 164 | float externalRadius = internalRadius + STROKE_SIZE; 165 | UIImage *someImageView = [UIImage alloc]; 166 | // This function returns a newImage, based on image, that has been: 167 | // - scaled to fit in (CGRect) rect 168 | // - and cropped within a circle of radius: rectWidth/2 169 | 170 | //Create the bitmap graphics context 171 | UIGraphicsBeginImageContextWithOptions(CGSizeMake(externalRadius*2, externalRadius*2), NO, 1.0); 172 | CGContextRef context = UIGraphicsGetCurrentContext(); 173 | CGContextSetFillColorWithColor(context, [clusterColor CGColor]); 174 | CGContextFillEllipseInRect(context, CGRectMake(0, 0, externalRadius*2, externalRadius*2)); 175 | CGContextSetFillColorWithColor(context, [UIColor.whiteColor CGColor]); 176 | CGContextFillEllipseInRect(context, CGRectMake(STROKE_SIZE, STROKE_SIZE, internalRadius*2, internalRadius*2)); 177 | [text drawInRect:CGRectMake(externalRadius - size.width/2, externalRadius - size.height/2, size.width, size.height) withAttributes:@{NSFontAttributeName: font, NSForegroundColorAttributeName: UIColor.blackColor }]; 178 | UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 179 | UIGraphicsEndImageContext(); 180 | 181 | return newImage; 182 | } 183 | 184 | - (void)onClusterAddedWithCluster:(nonnull YMKCluster *)cluster { 185 | NSNumber *myNum = @([cluster size]); 186 | [[cluster appearance] setIconWithImage:[self clusterImage:myNum]]; 187 | [cluster addClusterTapListenerWithClusterTapListener:self]; 188 | } 189 | 190 | - (BOOL)onClusterTapWithCluster:(nonnull YMKCluster *)cluster { 191 | NSMutableArray* lastKnownMarkers = [[NSMutableArray alloc] init]; 192 | for (YMKPlacemarkMapObject *placemark in [cluster placemarks]) { 193 | [lastKnownMarkers addObject:[placemark geometry]]; 194 | } 195 | [self fitMarkers:lastKnownMarkers]; 196 | return YES; 197 | } 198 | 199 | - (void)setInitialRegion:(NSDictionary *)initialParams { 200 | if (initializedRegion) return; 201 | if ([initialParams valueForKey:@"lat"] == nil || [initialParams valueForKey:@"lon"] == nil) return; 202 | 203 | float initialZoom = 10.f; 204 | float initialAzimuth = 0.f; 205 | float initialTilt = 0.f; 206 | 207 | if ([initialParams valueForKey:@"zoom"] != nil) initialZoom = [initialParams[@"zoom"] floatValue]; 208 | 209 | if ([initialParams valueForKey:@"azimuth"] != nil) initialTilt = [initialParams[@"azimuth"] floatValue]; 210 | 211 | if ([initialParams valueForKey:@"tilt"] != nil) initialTilt = [initialParams[@"tilt"] floatValue]; 212 | 213 | YMKPoint *initialRegionCenter = [RCTConvert YMKPoint:@{@"lat" : [initialParams valueForKey:@"lat"], @"lon" : [initialParams valueForKey:@"lon"]}]; 214 | YMKCameraPosition *initialRegioPosition = [YMKCameraPosition cameraPositionWithTarget:initialRegionCenter zoom:initialZoom azimuth:initialAzimuth tilt:initialTilt]; 215 | [self.mapWindow.map moveWithCameraPosition:initialRegioPosition]; 216 | initializedRegion = YES; 217 | } 218 | 219 | 220 | @synthesize reactTag; 221 | 222 | @end 223 | -------------------------------------------------------------------------------- /ios/ClusteredYamapView.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import "Converter/RCTConvert+Yamap.m" 5 | #import "ClusteredYamapView.h" 6 | #import "RNYamap.h" 7 | #import "View/RNCYMView.h" 8 | 9 | #ifndef MAX 10 | #import 11 | #endif 12 | 13 | @implementation ClusteredYamapView 14 | 15 | RCT_EXPORT_MODULE() 16 | 17 | - (NSArray *)supportedEvents { 18 | return @[ 19 | @"onRouteFound", 20 | @"onCameraPositionReceived", 21 | @"onVisibleRegionReceived", 22 | @"onCameraPositionChange", 23 | @"onMapPress", 24 | @"onMapLongPress", 25 | @"onCameraPositionChangeEnd" 26 | ]; 27 | } 28 | 29 | - (instancetype)init { 30 | self = [super init]; 31 | return self; 32 | } 33 | + (BOOL)requiresMainQueueSetup { 34 | return YES; 35 | } 36 | 37 | - (UIView *_Nullable)view { 38 | RNCYMView* map = [[RNCYMView alloc] init]; 39 | return map; 40 | } 41 | 42 | - (void)setCenterForMap:(RNCYMView*)map center:(NSDictionary*)_center zoom:(float)zoom azimuth:(float)azimuth tilt:(float)tilt duration:(float)duration animation:(int)animation { 43 | YMKPoint *center = [RCTConvert YMKPoint:_center]; 44 | YMKCameraPosition *pos = [YMKCameraPosition cameraPositionWithTarget:center zoom:zoom azimuth:azimuth tilt:tilt]; 45 | [map setCenter:pos withDuration:duration withAnimation:animation]; 46 | } 47 | 48 | // props 49 | RCT_EXPORT_VIEW_PROPERTY(onRouteFound, RCTBubblingEventBlock) 50 | RCT_EXPORT_VIEW_PROPERTY(onCameraPositionReceived, RCTBubblingEventBlock) 51 | RCT_EXPORT_VIEW_PROPERTY(onVisibleRegionReceived, RCTBubblingEventBlock) 52 | RCT_EXPORT_VIEW_PROPERTY(onCameraPositionChange, RCTBubblingEventBlock) 53 | RCT_EXPORT_VIEW_PROPERTY(onMapPress, RCTBubblingEventBlock) 54 | RCT_EXPORT_VIEW_PROPERTY(onMapLongPress, RCTBubblingEventBlock) 55 | RCT_EXPORT_VIEW_PROPERTY(onCameraPositionChangeEnd, RCTBubblingEventBlock) 56 | 57 | RCT_CUSTOM_VIEW_PROPERTY(initialRegion, NSDictionary, RNYMView) { 58 | if (json && view) { 59 | [view setInitialRegion:json]; 60 | } 61 | } 62 | RCT_CUSTOM_VIEW_PROPERTY(userLocationAccuracyFillColor, NSNumber, RNCYMView) { 63 | [view setUserLocationAccuracyFillColor:[RCTConvert UIColor:json]]; 64 | } 65 | 66 | RCT_CUSTOM_VIEW_PROPERTY(clusterColor, NSNumber, RNCYMView) { 67 | [view setClusterColor:[RCTConvert UIColor:json]]; 68 | } 69 | 70 | RCT_CUSTOM_VIEW_PROPERTY(clusteredMarkers, NSArray*_Nonnull, RNCYMView) { 71 | [view setClusteredMarkers:[RCTConvert NSArray:json]]; 72 | } 73 | 74 | RCT_CUSTOM_VIEW_PROPERTY(userLocationAccuracyStrokeColor, NSNumber, RNCYMView) { 75 | [view setUserLocationAccuracyStrokeColor:[RCTConvert UIColor:json]]; 76 | } 77 | 78 | 79 | RCT_CUSTOM_VIEW_PROPERTY(userLocationAccuracyStrokeWidth, NSNumber, RNCYMView) { 80 | [view setUserLocationAccuracyStrokeWidth:[json floatValue]]; 81 | } 82 | 83 | RCT_CUSTOM_VIEW_PROPERTY(userLocationIcon, NSString, RNCYMView) { 84 | if (json && view) { 85 | [view setUserLocationIcon:json]; 86 | } 87 | } 88 | 89 | RCT_CUSTOM_VIEW_PROPERTY(userLocationIconScale, NSNumber, RNCYMView) { 90 | if (json && view) { 91 | [view setUserLocationIconScale:json]; 92 | } 93 | } 94 | 95 | RCT_CUSTOM_VIEW_PROPERTY(showUserPosition, BOOL, RNCYMView) { 96 | if (view) { 97 | [view setListenUserLocation: json ? [json boolValue] : NO]; 98 | } 99 | } 100 | 101 | RCT_CUSTOM_VIEW_PROPERTY(followUser, BOOL, RNCYMView) { 102 | [view setFollowUser: json ? [json boolValue] : NO]; 103 | } 104 | 105 | RCT_CUSTOM_VIEW_PROPERTY(nightMode, BOOL, RNCYMView) { 106 | if (view) { 107 | [view setNightMode: json ? [json boolValue]: NO]; 108 | } 109 | } 110 | 111 | RCT_CUSTOM_VIEW_PROPERTY(mapStyle, NSString, RNCYMView) { 112 | if (json && view) { 113 | [view.mapWindow.map setMapStyleWithStyle:json]; 114 | } 115 | } 116 | 117 | RCT_CUSTOM_VIEW_PROPERTY(zoomGesturesEnabled, BOOL, RNCYMView) { 118 | if (view) { 119 | view.mapWindow.map.zoomGesturesEnabled = json ? [json boolValue] : YES; 120 | } 121 | } 122 | 123 | RCT_CUSTOM_VIEW_PROPERTY(scrollGesturesEnabled, BOOL, RNCYMView) { 124 | if (view) { 125 | view.mapWindow.map.scrollGesturesEnabled = json ? [json boolValue] : YES; 126 | } 127 | } 128 | 129 | RCT_CUSTOM_VIEW_PROPERTY(tiltGesturesEnabled, BOOL, RNCYMView) { 130 | if (view) { 131 | view.mapWindow.map.tiltGesturesEnabled = json ? [json boolValue] : YES; 132 | } 133 | } 134 | 135 | RCT_CUSTOM_VIEW_PROPERTY(rotateGesturesEnabled, BOOL, RNCYMView) { 136 | if (view) { 137 | view.mapWindow.map.rotateGesturesEnabled = json ? [json boolValue] : YES; 138 | } 139 | } 140 | 141 | RCT_CUSTOM_VIEW_PROPERTY(fastTapEnabled, BOOL, RNCYMView) { 142 | if (view) { 143 | view.mapWindow.map.fastTapEnabled = json ? [json boolValue] : YES; 144 | } 145 | } 146 | 147 | RCT_CUSTOM_VIEW_PROPERTY(mapType, NSString, RNCYMView) { 148 | if (view) { 149 | [view setMapType:json]; 150 | } 151 | } 152 | 153 | // ref 154 | RCT_EXPORT_METHOD(fitAllMarkers:(nonnull NSNumber*) reactTag) { 155 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 156 | RNCYMView *view = (RNCYMView*) viewRegistry[reactTag]; 157 | if (!view || ![view isKindOfClass:[RNCYMView class]]) { 158 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 159 | return; 160 | } 161 | [view fitAllMarkers]; 162 | }]; 163 | } 164 | 165 | RCT_EXPORT_METHOD(fitMarkers:(nonnull NSNumber *)reactTag json:(id)json) { 166 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 167 | RNYMView *view = (RNYMView *)viewRegistry[reactTag]; 168 | 169 | if (!view || ![view isKindOfClass:[RNYMView class]]) { 170 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 171 | return; 172 | } 173 | 174 | NSArray *points = [RCTConvert Points:json]; 175 | [view fitMarkers: points]; 176 | }]; 177 | } 178 | 179 | RCT_EXPORT_METHOD(findRoutes:(nonnull NSNumber*) reactTag json:(NSDictionary*) json) { 180 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 181 | RNCYMView *view = (RNCYMView*) viewRegistry[reactTag]; 182 | if (!view || ![view isKindOfClass:[RNCYMView class]]) { 183 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 184 | return; 185 | } 186 | NSArray* points = [RCTConvert Points:json[@"points"]]; 187 | NSMutableArray* requestPoints = [[NSMutableArray alloc] init]; 188 | for (int i = 0; i < [points count]; ++i) { 189 | YMKRequestPoint * requestPoint = [YMKRequestPoint requestPointWithPoint:[points objectAtIndex:i] type: YMKRequestPointTypeWaypoint pointContext:nil drivingArrivalPointId:nil indoorLevelId:nil]; 190 | [requestPoints addObject:requestPoint]; 191 | } 192 | NSArray* vehicles = [RCTConvert Vehicles:json[@"vehicles"]]; 193 | [view findRoutes: requestPoints vehicles: vehicles withId:json[@"id"]]; 194 | }]; 195 | } 196 | 197 | RCT_EXPORT_METHOD(setCenter:(nonnull NSNumber*) reactTag center:(NSDictionary*_Nonnull) center zoom:(NSNumber*_Nonnull) zoom azimuth:(NSNumber*_Nonnull) azimuth tilt:(NSNumber*_Nonnull) tilt duration: (NSNumber*_Nonnull) duration animation:(NSNumber*_Nonnull) animation) { 198 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 199 | RNCYMView *view = (RNCYMView*) viewRegistry[reactTag]; 200 | if (!view || ![view isKindOfClass:[RNCYMView class]]) { 201 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 202 | return; 203 | } 204 | [self setCenterForMap: view center:center zoom: [zoom floatValue] azimuth: [azimuth floatValue] tilt: [tilt floatValue] duration: [duration floatValue] animation: [animation intValue]]; 205 | }]; 206 | } 207 | 208 | RCT_EXPORT_METHOD(setZoom:(nonnull NSNumber*) reactTag zoom:(NSNumber*_Nonnull) zoom duration:(NSNumber*_Nonnull) duration animation:(NSNumber*_Nonnull) animation) { 209 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 210 | RNCYMView *view = (RNCYMView*) viewRegistry[reactTag]; 211 | if (!view || ![view isKindOfClass:[RNCYMView class]]) { 212 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 213 | return; 214 | } 215 | [view setZoom: [zoom floatValue] withDuration:[duration floatValue] withAnimation:[animation intValue]]; 216 | }]; 217 | } 218 | 219 | RCT_EXPORT_METHOD(getCameraPosition:(nonnull NSNumber*) reactTag _id:(NSString*_Nonnull) _id) { 220 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 221 | RNCYMView *view = (RNCYMView*) viewRegistry[reactTag]; 222 | if (!view || ![view isKindOfClass:[RNCYMView class]]) { 223 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 224 | return; 225 | } 226 | [view emitCameraPositionToJS:_id]; 227 | }]; 228 | } 229 | 230 | RCT_EXPORT_METHOD(getVisibleRegion:(nonnull NSNumber*) reactTag _id:(NSString*_Nonnull) _id) { 231 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 232 | RNCYMView *view = (RNCYMView*) viewRegistry[reactTag]; 233 | if (!view || ![view isKindOfClass:[RNCYMView class]]) { 234 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 235 | return; 236 | } 237 | [view emitVisibleRegionToJS:_id]; 238 | }]; 239 | } 240 | 241 | @end 242 | -------------------------------------------------------------------------------- /src/components/ClusteredYamap.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Platform, 4 | requireNativeComponent, 5 | NativeModules, 6 | UIManager, 7 | findNodeHandle, 8 | ViewProps, 9 | ImageSourcePropType, 10 | NativeSyntheticEvent, 11 | ListRenderItemInfo 12 | } from 'react-native'; 13 | // @ts-ignore 14 | import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; 15 | import CallbacksManager from '../utils/CallbacksManager'; 16 | import { 17 | Animation, 18 | Point, 19 | DrivingInfo, 20 | MasstransitInfo, 21 | RoutesFoundEvent, 22 | Vehicles, 23 | CameraPosition, 24 | VisibleRegion, 25 | ScreenPoint, 26 | MapLoaded, 27 | InitialRegion, 28 | YandexLogoPosition, 29 | YandexLogoPadding 30 | } from '../interfaces'; 31 | import { processColorProps } from '../utils'; 32 | import { YaMap } from './Yamap'; 33 | 34 | const { yamap: NativeYamapModule } = NativeModules; 35 | 36 | export interface ClusteredYaMapProps extends ViewProps { 37 | userLocationIcon?: ImageSourcePropType; 38 | userLocationIconScale?: number; 39 | clusteredMarkers: ReadonlyArray<{point: Point, data: T}> 40 | renderMarker: (info: {point: Point, data: ListRenderItemInfo}, index: number) => React.ReactElement 41 | clusterColor?: string; 42 | showUserPosition?: boolean; 43 | nightMode?: boolean; 44 | mapStyle?: string; 45 | onCameraPositionChange?: (event: NativeSyntheticEvent) => void; 46 | onCameraPositionChangeEnd?: (event: NativeSyntheticEvent) => void; 47 | onMapPress?: (event: NativeSyntheticEvent) => void; 48 | onMapLongPress?: (event: NativeSyntheticEvent) => void; 49 | onMapLoaded?: (event: NativeSyntheticEvent) => void; 50 | userLocationAccuracyFillColor?: string; 51 | userLocationAccuracyStrokeColor?: string; 52 | userLocationAccuracyStrokeWidth?: number; 53 | scrollGesturesEnabled?: boolean; 54 | zoomGesturesEnabled?: boolean; 55 | tiltGesturesEnabled?: boolean; 56 | rotateGesturesEnabled?: boolean; 57 | fastTapEnabled?: boolean; 58 | initialRegion?: InitialRegion; 59 | maxFps?: number; 60 | followUser?: boolean; 61 | logoPosition?: YandexLogoPosition; 62 | logoPadding?: YandexLogoPadding; 63 | } 64 | 65 | const YaMapNativeComponent = requireNativeComponent & {clusteredMarkers: Point[]}>('ClusteredYamapView'); 66 | 67 | export class ClusteredYamap extends React.Component { 68 | static defaultProps = { 69 | showUserPosition: true, 70 | clusterColor: 'red', 71 | maxFps: 60 72 | }; 73 | 74 | // @ts-ignore 75 | map = React.createRef(); 76 | 77 | static ALL_MASSTRANSIT_VEHICLES: Vehicles[] = [ 78 | 'bus', 79 | 'trolleybus', 80 | 'tramway', 81 | 'minibus', 82 | 'suburban', 83 | 'underground', 84 | 'ferry', 85 | 'cable', 86 | 'funicular', 87 | ]; 88 | 89 | public static init(apiKey: string): Promise { 90 | return NativeYamapModule.init(apiKey); 91 | } 92 | 93 | public static setLocale(locale: string): Promise { 94 | return new Promise((resolve, reject) => { 95 | NativeYamapModule.setLocale(locale, () => resolve(), (err: string) => reject(new Error(err))); 96 | }); 97 | } 98 | 99 | public static getLocale(): Promise { 100 | return new Promise((resolve, reject) => { 101 | NativeYamapModule.getLocale((locale: string) => resolve(locale), (err: string) => reject(new Error(err))); 102 | }); 103 | } 104 | 105 | public static resetLocale(): Promise { 106 | return new Promise((resolve, reject) => { 107 | NativeYamapModule.resetLocale(() => resolve(), (err: string) => reject(new Error(err))); 108 | }); 109 | } 110 | 111 | public findRoutes(points: Point[], vehicles: Vehicles[], callback: (event: RoutesFoundEvent) => void) { 112 | this._findRoutes(points, vehicles, callback); 113 | } 114 | 115 | public findMasstransitRoutes(points: Point[], callback: (event: RoutesFoundEvent) => void) { 116 | this._findRoutes(points, YaMap.ALL_MASSTRANSIT_VEHICLES, callback); 117 | } 118 | 119 | public findPedestrianRoutes(points: Point[], callback: (event: RoutesFoundEvent) => void) { 120 | this._findRoutes(points, [], callback); 121 | } 122 | 123 | public findDrivingRoutes(points: Point[], callback: (event: RoutesFoundEvent) => void) { 124 | this._findRoutes(points, ['car'], callback); 125 | } 126 | 127 | public fitAllMarkers() { 128 | UIManager.dispatchViewManagerCommand( 129 | findNodeHandle(this), 130 | this.getCommand('fitAllMarkers'), 131 | [] 132 | ); 133 | } 134 | 135 | public setTrafficVisible(isVisible: boolean) { 136 | UIManager.dispatchViewManagerCommand( 137 | findNodeHandle(this), 138 | this.getCommand('setTrafficVisible'), 139 | [isVisible] 140 | ); 141 | } 142 | 143 | public fitMarkers(points: Point[]) { 144 | UIManager.dispatchViewManagerCommand( 145 | findNodeHandle(this), 146 | this.getCommand('fitMarkers'), 147 | [points] 148 | ); 149 | } 150 | 151 | public setCenter(center: { lon: number, lat: number, zoom?: number }, zoom: number = center.zoom || 10, azimuth: number = 0, tilt: number = 0, duration: number = 0, animation: Animation = Animation.SMOOTH) { 152 | UIManager.dispatchViewManagerCommand( 153 | findNodeHandle(this), 154 | this.getCommand('setCenter'), 155 | [center, zoom, azimuth, tilt, duration, animation] 156 | ); 157 | } 158 | 159 | public setZoom(zoom: number, duration: number = 0, animation: Animation = Animation.SMOOTH) { 160 | UIManager.dispatchViewManagerCommand( 161 | findNodeHandle(this), 162 | this.getCommand('setZoom'), 163 | [zoom, duration, animation] 164 | ); 165 | } 166 | 167 | public getCameraPosition(callback: (position: CameraPosition) => void) { 168 | const cbId = CallbacksManager.addCallback(callback); 169 | UIManager.dispatchViewManagerCommand( 170 | findNodeHandle(this), 171 | this.getCommand('getCameraPosition'), 172 | [cbId] 173 | ); 174 | } 175 | 176 | public getVisibleRegion(callback: (VisibleRegion: VisibleRegion) => void) { 177 | const cbId = CallbacksManager.addCallback(callback); 178 | UIManager.dispatchViewManagerCommand( 179 | findNodeHandle(this), 180 | this.getCommand('getVisibleRegion'), 181 | [cbId] 182 | ); 183 | } 184 | 185 | public getScreenPoints(points: Point[], callback: (screenPoint: ScreenPoint) => void) { 186 | const cbId = CallbacksManager.addCallback(callback); 187 | UIManager.dispatchViewManagerCommand( 188 | findNodeHandle(this), 189 | this.getCommand('getScreenPoints'), 190 | [points, cbId] 191 | ); 192 | } 193 | 194 | public getWorldPoints(points: ScreenPoint[], callback: (point: Point) => void) { 195 | const cbId = CallbacksManager.addCallback(callback); 196 | UIManager.dispatchViewManagerCommand( 197 | findNodeHandle(this), 198 | this.getCommand('getWorldPoints'), 199 | [points, cbId] 200 | ); 201 | } 202 | 203 | private _findRoutes(points: Point[], vehicles: Vehicles[], callback: ((event: RoutesFoundEvent) => void) | ((event: RoutesFoundEvent) => void) | ((event: RoutesFoundEvent) => void)) { 204 | const cbId = CallbacksManager.addCallback(callback); 205 | const args = Platform.OS === 'ios' ? [{ points, vehicles, id: cbId }] : [points, vehicles, cbId]; 206 | 207 | UIManager.dispatchViewManagerCommand( 208 | findNodeHandle(this), 209 | this.getCommand('findRoutes'), 210 | args 211 | ); 212 | } 213 | 214 | private getCommand(cmd: string): any { 215 | return Platform.OS === 'ios' ? UIManager.getViewManagerConfig('ClusteredYamapView').Commands[cmd] : cmd; 216 | } 217 | 218 | private processRoute(event: NativeSyntheticEvent<{ id: string } & RoutesFoundEvent>) { 219 | const { id, ...routes } = event.nativeEvent; 220 | CallbacksManager.call(id, routes); 221 | } 222 | 223 | private processCameraPosition(event: NativeSyntheticEvent<{ id: string } & CameraPosition>) { 224 | const { id, ...point } = event.nativeEvent; 225 | CallbacksManager.call(id, point); 226 | } 227 | 228 | private processVisibleRegion(event: NativeSyntheticEvent<{ id: string } & VisibleRegion>) { 229 | const { id, ...visibleRegion } = event.nativeEvent; 230 | CallbacksManager.call(id, visibleRegion); 231 | } 232 | 233 | private processWorldToScreenPointsReceived(event: NativeSyntheticEvent<{ id: string } & ScreenPoint[]>) { 234 | const { id, ...screenPoints } = event.nativeEvent; 235 | CallbacksManager.call(id, screenPoints); 236 | } 237 | 238 | private processScreenToWorldPointsReceived(event: NativeSyntheticEvent<{ id: string } & Point[]>) { 239 | const { id, ...worldPoints } = event.nativeEvent; 240 | CallbacksManager.call(id, worldPoints); 241 | } 242 | 243 | private resolveImageUri(img: ImageSourcePropType) { 244 | return img ? resolveAssetSource(img).uri : ''; 245 | } 246 | 247 | private getProps() { 248 | const props = { 249 | ...this.props, 250 | clusteredMarkers: this.props.clusteredMarkers.map(mark => mark.point), 251 | children: this.props.clusteredMarkers.map(this.props.renderMarker), 252 | onRouteFound: this.processRoute, 253 | onCameraPositionReceived: this.processCameraPosition, 254 | onVisibleRegionReceived: this.processVisibleRegion, 255 | onWorldToScreenPointsReceived: this.processWorldToScreenPointsReceived, 256 | onScreenToWorldPointsReceived: this.processScreenToWorldPointsReceived, 257 | userLocationIcon: this.props.userLocationIcon ? this.resolveImageUri(this.props.userLocationIcon) : undefined 258 | }; 259 | processColorProps(props, 'clusterColor' as keyof ClusteredYaMapProps); 260 | processColorProps(props, 'userLocationAccuracyFillColor' as keyof ClusteredYaMapProps); 261 | processColorProps(props, 'userLocationAccuracyStrokeColor' as keyof ClusteredYaMapProps); 262 | 263 | return props; 264 | } 265 | 266 | render() { 267 | return ( 268 | 272 | ); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /ios/YamapSearch.swift: -------------------------------------------------------------------------------- 1 | import YandexMapsMobile 2 | import UIKit 3 | 4 | @objc(YamapSearch) 5 | class YamapSearch: NSObject { 6 | enum ArrayError: Error { 7 | case indexOutOfBounds 8 | } 9 | 10 | var searchManager: YMKSearchManager? 11 | let defaultBoundingBox: YMKBoundingBox 12 | var searchSession: YMKSearchSession? 13 | var searchOptions: YMKSearchOptions 14 | 15 | let ERR_NO_REQUEST_ARG = "YANDEX_SEARCH_ERR_NO_REQUEST_ARG" 16 | let ERR_SEARCH_FAILED = "YANDEX_SEARCH_ERR_SEARCH_FAILED" 17 | let YandexSuggestErrorDomain = "YandexSuggestErrorDomain" 18 | 19 | override init() { 20 | let southWestPoint = YMKPoint(latitude: -90.0, longitude: -180.0) 21 | let northEastPoint = YMKPoint(latitude: -85.0, longitude: -175.0) 22 | self.defaultBoundingBox = YMKBoundingBox(southWest: southWestPoint, northEast: northEastPoint) 23 | self.searchOptions = YMKSearchOptions() 24 | super.init() 25 | } 26 | 27 | private func setSearchOptions(options: [String: Any]?) -> Void { 28 | self.searchOptions = YMKSearchOptions(); 29 | if ((options?.keys) != nil) { 30 | for (key, value) in options! { 31 | self.searchOptions.setValue(value, forKey: key) 32 | } 33 | } 34 | } 35 | 36 | func runOnMainQueueWithoutDeadlocking(_ block: @escaping () -> Void) { 37 | if Thread.isMainThread { 38 | block() 39 | } else { 40 | DispatchQueue.main.sync(execute: block) 41 | } 42 | } 43 | 44 | func initSearchManager() -> Void { 45 | if searchManager == nil { 46 | runOnMainQueueWithoutDeadlocking { 47 | self.searchManager = YMKSearchFactory.instance().createSearchManager(with: .online) 48 | } 49 | } 50 | } 51 | 52 | private func getGeometry(figure: [String: Any]?) throws -> YMKGeometry { 53 | if (figure == nil) { 54 | return YMKGeometry(boundingBox: self.defaultBoundingBox) 55 | } 56 | if (figure!["type"] as! String=="POINT") { 57 | return YMKGeometry.init(point: YMKPoint(latitude: (figure!["value"] as! [String: Any])["lat"] as! Double, longitude: (figure!["value"] as! [String: Any])["lon"] as! Double)) 58 | } 59 | if (figure!["type"] as! String=="BOUNDINGBOX") { 60 | var southWest = YMKPoint(latitude: ((figure!["value"] as! [String: Any])["southWest"] as! [String: Any])["lat"] as! Double, longitude: ((figure!["value"] as! [String: Any])["southWest"] as! [String: Any])["lon"] as! Double) 61 | var northEast = YMKPoint(latitude: ((figure!["value"] as! [String: Any])["northEast"] as! [String: Any])["lat"] as! Double, longitude: ((figure!["value"] as! [String: Any])["northEast"] as! [String: Any])["lon"] as! Double) 62 | return YMKGeometry.init(boundingBox: YMKBoundingBox(southWest: southWest, northEast: northEast)) 63 | } 64 | if (figure!["type"] as! String=="POLYLINE") { 65 | let points = (figure!["value"] as! [String: Any])["points"] as! [[String: Any]]; 66 | var convertedPoints = [YMKPoint]() 67 | points.forEach{point in 68 | convertedPoints.append(YMKPoint(latitude: point["lat"] as! Double, longitude: point["lon"] as! Double)) 69 | } 70 | return YMKGeometry.init(polyline: YMKPolyline(points:convertedPoints)) 71 | } 72 | if (figure!["type"] as! String=="POLYGON") { 73 | let linearRingPoints = (figure!["value"] as! [String: Any])["points"] as! [[String: Any]]; 74 | if (linearRingPoints.count != 4) { 75 | throw ArrayError.indexOutOfBounds 76 | } 77 | var convertedlinearRingPoints = [YMKPoint]() 78 | linearRingPoints.forEach{point in 79 | convertedlinearRingPoints.append(YMKPoint(latitude: point["lat"] as! Double, longitude: point["lon"] as! Double)) 80 | } 81 | return YMKGeometry.init(polygon: YMKPolygon(outerRing: YMKLinearRing(points: convertedlinearRingPoints), innerRings: [])) 82 | } 83 | return YMKGeometry(boundingBox: self.defaultBoundingBox) 84 | } 85 | 86 | private func convertSearchResponce(search: YMKSearchResponse?) -> [String: Any] { 87 | var searchToPass = [String: Any]() 88 | let geoObjects = search?.collection.children.compactMap { $0.obj } 89 | 90 | searchToPass["formatted"] = ( 91 | geoObjects?.first?.metadataContainer 92 | .getItemOf(YMKSearchToponymObjectMetadata.self) as? YMKSearchToponymObjectMetadata 93 | )?.address.formattedAddress 94 | 95 | searchToPass["country_code"] = ( 96 | geoObjects?.first?.metadataContainer 97 | .getItemOf(YMKSearchToponymObjectMetadata.self) as? YMKSearchToponymObjectMetadata 98 | )?.address.countryCode 99 | 100 | var components = [[String: Any]]() 101 | 102 | geoObjects?.forEach { geoItem in 103 | var component = [String: Any]() 104 | component["name"] = geoItem.name 105 | component["kind"] = ( 106 | geoItem.metadataContainer 107 | .getItemOf(YMKSearchToponymObjectMetadata.self) as? YMKSearchToponymObjectMetadata 108 | )?.address.components.last?.kinds.first?.stringValue 109 | components.append(component) 110 | } 111 | 112 | searchToPass["Components"] = components 113 | searchToPass["uri"] = ( 114 | geoObjects?.first?.metadataContainer.getItemOf(YMKUriObjectMetadata.self) as? YMKUriObjectMetadata 115 | )?.uris.first?.value 116 | 117 | return searchToPass; 118 | } 119 | 120 | @objc func searchByAddress(_ searchQuery: String, figure: [String: Any]?, options: [String: Any]?, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) { 121 | self.initSearchManager() 122 | do { 123 | self.setSearchOptions(options: options) 124 | let geometryFigure: YMKGeometry = try self.getGeometry(figure: figure) 125 | runOnMainQueueWithoutDeadlocking { 126 | self.searchSession = self.searchManager?.submit(withText: searchQuery, geometry: geometryFigure, searchOptions: self.searchOptions, responseHandler: { search, error in 127 | if let error = error { 128 | rejecter(self.ERR_SEARCH_FAILED, "search request: \(searchQuery)", error) 129 | return 130 | } 131 | 132 | resolver(self.convertSearchResponce(search: search)) 133 | }) 134 | } 135 | } catch { 136 | rejecter(ERR_NO_REQUEST_ARG, "search request: \(searchQuery)", nil) 137 | } 138 | } 139 | 140 | @objc func addressToGeo(_ searchQuery: String, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) { 141 | self.initSearchManager() 142 | do { 143 | self.setSearchOptions(options: nil) 144 | runOnMainQueueWithoutDeadlocking { 145 | self.searchSession = self.searchManager?.submit(withText: searchQuery, geometry: YMKGeometry(boundingBox: self.defaultBoundingBox), searchOptions: self.searchOptions, responseHandler: { search, error in 146 | if let error = error { 147 | rejecter(self.ERR_SEARCH_FAILED, "search request: \(searchQuery)", error) 148 | return 149 | } 150 | 151 | let geoObjects = search?.collection.children.compactMap { $0.obj } 152 | 153 | let point = ( 154 | geoObjects?.first?.metadataContainer 155 | .getItemOf(YMKSearchToponymObjectMetadata.self) as? YMKSearchToponymObjectMetadata 156 | )?.balloonPoint 157 | let searchPoint = ["lat": point?.latitude, "lon": point?.longitude]; 158 | 159 | resolver(searchPoint) 160 | 161 | }) 162 | } 163 | } catch { 164 | rejecter(ERR_NO_REQUEST_ARG, "search request: \(searchQuery)", nil) 165 | } 166 | } 167 | 168 | @objc func searchByPoint(_ point: [String: Any], zoom: NSNumber, options: [String: Any]?, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) { 169 | let searchPoint = YMKPoint(latitude: point["lat"] as! Double, longitude: point["lat"] as! Double) 170 | self.initSearchManager() 171 | self.setSearchOptions(options: options) 172 | runOnMainQueueWithoutDeadlocking { 173 | self.searchSession = self.searchManager?.submit(with: searchPoint, zoom: zoom, searchOptions: self.searchOptions, responseHandler: { search, error in 174 | if let error = error { 175 | rejecter(self.ERR_SEARCH_FAILED, "search request: \(point)", error) 176 | return 177 | } 178 | 179 | resolver(self.convertSearchResponce(search: search)) 180 | }) 181 | } 182 | } 183 | 184 | @objc func geoToAddress(_ point: [String: Any], resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) { 185 | let searchPoint = YMKPoint(latitude: point["lat"] as! Double, longitude: point["lat"] as! Double) 186 | self.initSearchManager() 187 | self.setSearchOptions(options: nil) 188 | runOnMainQueueWithoutDeadlocking { 189 | self.searchSession = self.searchManager?.submit(with: searchPoint, zoom: 10, searchOptions: self.searchOptions, responseHandler: { search, error in 190 | if let error = error { 191 | rejecter(self.ERR_SEARCH_FAILED, "search request: \(point)", error) 192 | return 193 | } 194 | 195 | resolver(self.convertSearchResponce(search: search)) 196 | }) 197 | } 198 | } 199 | 200 | @objc func searchByURI(_ searchUri: NSString, options: [String: Any]?, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) { 201 | self.initSearchManager() 202 | self.setSearchOptions(options: options) 203 | runOnMainQueueWithoutDeadlocking { 204 | self.searchSession = self.searchManager?.searchByURI(withUri: searchUri as String, searchOptions: self.searchOptions, responseHandler: { search, error in 205 | if let error = error { 206 | rejecter(self.ERR_SEARCH_FAILED, "search request: \(searchUri)", error) 207 | return 208 | } 209 | 210 | resolver(self.convertSearchResponce(search: search)) 211 | }) 212 | } 213 | } 214 | 215 | @objc func resolveURI(_ searchUri: NSString, options: [String: Any]?, resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) { 216 | self.initSearchManager() 217 | self.setSearchOptions(options: options) 218 | runOnMainQueueWithoutDeadlocking { 219 | self.searchSession = self.searchManager?.resolveURI(withUri: searchUri as String, searchOptions: self.searchOptions, responseHandler: { search, error in 220 | if let error = error { 221 | rejecter(self.ERR_SEARCH_FAILED, "search request: \(searchUri)", error) 222 | return 223 | } 224 | 225 | resolver(self.convertSearchResponce(search: search)) 226 | }) 227 | } 228 | } 229 | 230 | @objc static func moduleName() -> String { 231 | return "YamapSearch" 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /ios/YamapView.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import 4 | #import "Converter/RCTConvert+Yamap.m" 5 | #import "YamapView.h" 6 | #import "RNYamap.h" 7 | #import "View/RNYMView.h" 8 | 9 | #ifndef MAX 10 | #import 11 | #endif 12 | 13 | @implementation YamapView 14 | 15 | RCT_EXPORT_MODULE() 16 | 17 | - (NSArray*)supportedEvents { 18 | return @[ 19 | @"onRouteFound", 20 | @"onCameraPositionReceived", 21 | @"onVisibleRegionReceived", 22 | @"onCameraPositionChange", 23 | @"onCameraPositionChangeEnd", 24 | @"onMapPress", 25 | @"onMapLongPress", 26 | @"onMapLoaded", 27 | @"onWorldToScreenPointsReceived", 28 | @"onScreenToWorldPointsReceived" 29 | ]; 30 | } 31 | 32 | - (instancetype)init { 33 | self = [super init]; 34 | 35 | return self; 36 | } 37 | 38 | + (BOOL)requiresMainQueueSetup { 39 | return YES; 40 | } 41 | 42 | - (UIView*_Nullable)view { 43 | RNYMView *map = [[RNYMView alloc] init]; 44 | 45 | return map; 46 | } 47 | 48 | - (void)setCenterForMap:(RNYMView*)map center:(NSDictionary*)_center zoom:(float)zoom azimuth:(float)azimuth tilt:(float)tilt duration:(float)duration animation:(int)animation { 49 | YMKPoint *center = [RCTConvert YMKPoint:_center]; 50 | YMKCameraPosition *pos = [YMKCameraPosition cameraPositionWithTarget:center zoom:zoom azimuth:azimuth tilt:tilt]; 51 | [map setCenter:pos withDuration:duration withAnimation:animation]; 52 | } 53 | 54 | // PROPS 55 | RCT_EXPORT_VIEW_PROPERTY(onRouteFound, RCTBubblingEventBlock) 56 | RCT_EXPORT_VIEW_PROPERTY(onCameraPositionReceived, RCTBubblingEventBlock) 57 | RCT_EXPORT_VIEW_PROPERTY(onVisibleRegionReceived, RCTBubblingEventBlock) 58 | RCT_EXPORT_VIEW_PROPERTY(onCameraPositionChange, RCTBubblingEventBlock) 59 | RCT_EXPORT_VIEW_PROPERTY(onCameraPositionChangeEnd, RCTBubblingEventBlock) 60 | RCT_EXPORT_VIEW_PROPERTY(onMapPress, RCTBubblingEventBlock) 61 | RCT_EXPORT_VIEW_PROPERTY(onMapLongPress, RCTBubblingEventBlock) 62 | RCT_EXPORT_VIEW_PROPERTY(onMapLoaded, RCTBubblingEventBlock) 63 | RCT_EXPORT_VIEW_PROPERTY(onWorldToScreenPointsReceived, RCTBubblingEventBlock) 64 | RCT_EXPORT_VIEW_PROPERTY(onScreenToWorldPointsReceived, RCTBubblingEventBlock) 65 | 66 | RCT_CUSTOM_VIEW_PROPERTY(userLocationAccuracyFillColor, NSNumber, RNYMView) { 67 | [view setUserLocationAccuracyFillColor:[RCTConvert UIColor:json]]; 68 | } 69 | 70 | RCT_CUSTOM_VIEW_PROPERTY(userLocationAccuracyStrokeColor, NSNumber, RNYMView) { 71 | [view setUserLocationAccuracyStrokeColor:[RCTConvert UIColor:json]]; 72 | } 73 | 74 | RCT_CUSTOM_VIEW_PROPERTY(userLocationAccuracyStrokeWidth, NSNumber, RNYMView) { 75 | [view setUserLocationAccuracyStrokeWidth:[json floatValue]]; 76 | } 77 | 78 | RCT_CUSTOM_VIEW_PROPERTY(userLocationIcon, NSString, RNYMView) { 79 | if (json && view) { 80 | [view setUserLocationIcon:json]; 81 | } 82 | } 83 | 84 | RCT_CUSTOM_VIEW_PROPERTY(userLocationIconScale, NSNumber, RNYMView) { 85 | if (json && view) { 86 | [view setUserLocationIconScale:json]; 87 | } 88 | } 89 | 90 | RCT_CUSTOM_VIEW_PROPERTY(showUserPosition, BOOL, RNYMView) { 91 | if (view) { 92 | [view setListenUserLocation: json ? [json boolValue] : NO]; 93 | } 94 | } 95 | 96 | RCT_CUSTOM_VIEW_PROPERTY(nightMode, BOOL, RNYMView) { 97 | if (view) { 98 | [view setNightMode: json ? [json boolValue]: NO]; 99 | } 100 | } 101 | 102 | RCT_CUSTOM_VIEW_PROPERTY(mapStyle, NSString, RNYMView) { 103 | if (json && view) { 104 | [view.mapWindow.map setMapStyleWithStyle:json]; 105 | } 106 | } 107 | 108 | RCT_CUSTOM_VIEW_PROPERTY(zoomGesturesEnabled, BOOL, RNYMView) { 109 | if (view) { 110 | view.mapWindow.map.zoomGesturesEnabled = json ? [json boolValue] : YES; 111 | } 112 | } 113 | 114 | RCT_CUSTOM_VIEW_PROPERTY(scrollGesturesEnabled, BOOL, RNYMView) { 115 | if (view) { 116 | view.mapWindow.map.scrollGesturesEnabled = json ? [json boolValue] : YES; 117 | } 118 | } 119 | 120 | RCT_CUSTOM_VIEW_PROPERTY(tiltGesturesEnabled, BOOL, RNYMView) { 121 | if (view) { 122 | view.mapWindow.map.tiltGesturesEnabled = json ? [json boolValue] : YES; 123 | } 124 | } 125 | 126 | RCT_CUSTOM_VIEW_PROPERTY(rotateGesturesEnabled, BOOL, RNYMView) { 127 | if (view) { 128 | view.mapWindow.map.rotateGesturesEnabled = json ? [json boolValue] : YES; 129 | } 130 | } 131 | 132 | RCT_CUSTOM_VIEW_PROPERTY(fastTapEnabled, BOOL, RNYMView) { 133 | if (view) { 134 | view.mapWindow.map.fastTapEnabled = json ? [json boolValue] : YES; 135 | } 136 | } 137 | 138 | RCT_CUSTOM_VIEW_PROPERTY(mapType, NSString, RNYMView) { 139 | if (view) { 140 | [view setMapType:json]; 141 | } 142 | } 143 | 144 | RCT_CUSTOM_VIEW_PROPERTY(initialRegion, NSDictionary, RNYMView) { 145 | if (json && view) { 146 | [view setInitialRegion:json]; 147 | } 148 | } 149 | 150 | RCT_CUSTOM_VIEW_PROPERTY(maxFps, NSNumber, RNYMView) { 151 | if (json && view) { 152 | [view setMaxFps:[json floatValue]]; 153 | } 154 | } 155 | 156 | RCT_CUSTOM_VIEW_PROPERTY(interactive, BOOL, RNYMView) { 157 | if (json && view) { 158 | [view setInteractive:[json boolValue]]; 159 | } 160 | } 161 | 162 | RCT_CUSTOM_VIEW_PROPERTY(logoPosition, BOOL, RNYMView) { 163 | if (json && view) { 164 | [view setLogoPosition:json]; 165 | } 166 | } 167 | 168 | RCT_CUSTOM_VIEW_PROPERTY(logoPadding, BOOL, RNYMView) { 169 | if (json && view) { 170 | [view setLogoPadding:json]; 171 | } 172 | } 173 | 174 | // REF 175 | RCT_EXPORT_METHOD(fitAllMarkers:(nonnull NSNumber *)reactTag) { 176 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 177 | RNYMView *view = (RNYMView *)viewRegistry[reactTag]; 178 | 179 | if (!view || ![view isKindOfClass:[RNYMView class]]) { 180 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 181 | return; 182 | } 183 | 184 | [view fitAllMarkers]; 185 | }]; 186 | } 187 | 188 | RCT_EXPORT_METHOD(fitMarkers:(nonnull NSNumber *)reactTag json:(id)json) { 189 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 190 | RNYMView *view = (RNYMView *)viewRegistry[reactTag]; 191 | 192 | if (!view || ![view isKindOfClass:[RNYMView class]]) { 193 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 194 | return; 195 | } 196 | 197 | NSArray *points = [RCTConvert Points:json]; 198 | [view fitMarkers: points]; 199 | }]; 200 | } 201 | 202 | RCT_EXPORT_METHOD(findRoutes:(nonnull NSNumber *)reactTag json:(NSDictionary *)json) { 203 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 204 | RNYMView *view = (RNYMView *)viewRegistry[reactTag]; 205 | 206 | if (!view || ![view isKindOfClass:[RNYMView class]]) { 207 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 208 | return; 209 | } 210 | 211 | NSArray *points = [RCTConvert Points:json[@"points"]]; 212 | NSMutableArray *requestPoints = [[NSMutableArray alloc] init]; 213 | 214 | for (int i = 0; i < [points count]; ++i) { 215 | YMKRequestPoint *requestPoint = [YMKRequestPoint requestPointWithPoint:[points objectAtIndex:i] type:YMKRequestPointTypeWaypoint pointContext:nil drivingArrivalPointId:nil indoorLevelId:nil]; 216 | [requestPoints addObject:requestPoint]; 217 | } 218 | 219 | NSArray *vehicles = [RCTConvert Vehicles:json[@"vehicles"]]; 220 | [view findRoutes: requestPoints vehicles: vehicles withId:json[@"id"]]; 221 | }]; 222 | } 223 | 224 | RCT_EXPORT_METHOD(setCenter:(nonnull NSNumber *)reactTag center:(NSDictionary *_Nonnull)center zoom:(NSNumber *_Nonnull)zoom azimuth:(NSNumber *_Nonnull)azimuth tilt:(NSNumber *_Nonnull)tilt duration:(NSNumber *_Nonnull)duration animation:(NSNumber *_Nonnull)animation) { 225 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 226 | RNYMView *view = (RNYMView *)viewRegistry[reactTag]; 227 | 228 | if (!view || ![view isKindOfClass:[RNYMView class]]) { 229 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 230 | return; 231 | } 232 | 233 | [self setCenterForMap:view center:center zoom:[zoom floatValue] azimuth:[azimuth floatValue] tilt:[tilt floatValue] duration:[duration floatValue] animation:[animation intValue]]; 234 | }]; 235 | } 236 | 237 | RCT_EXPORT_METHOD(setZoom:(nonnull NSNumber *)reactTag zoom:(NSNumber *_Nonnull)zoom duration:(NSNumber *_Nonnull)duration animation:(NSNumber *_Nonnull)animation) { 238 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 239 | RNYMView *view = (RNYMView *)viewRegistry[reactTag]; 240 | 241 | if (!view || ![view isKindOfClass:[RNYMView class]]) { 242 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 243 | return; 244 | } 245 | 246 | [view setZoom:[zoom floatValue] withDuration:[duration floatValue] withAnimation:[animation intValue]]; 247 | }]; 248 | } 249 | 250 | RCT_EXPORT_METHOD(getCameraPosition:(nonnull NSNumber *)reactTag _id:(NSString *_Nonnull)_id) { 251 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 252 | RNYMView *view = (RNYMView *)viewRegistry[reactTag]; 253 | 254 | if (!view || ![view isKindOfClass:[RNYMView class]]) { 255 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 256 | return; 257 | } 258 | 259 | [view emitCameraPositionToJS:_id]; 260 | }]; 261 | } 262 | 263 | RCT_EXPORT_METHOD(getVisibleRegion:(nonnull NSNumber *)reactTag _id:(NSString *_Nonnull)_id) { 264 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 265 | RNYMView *view = (RNYMView *)viewRegistry[reactTag]; 266 | 267 | if (!view || ![view isKindOfClass:[RNYMView class]]) { 268 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 269 | return; 270 | } 271 | 272 | [view emitVisibleRegionToJS:_id]; 273 | }]; 274 | } 275 | 276 | RCT_EXPORT_METHOD(setTrafficVisible:(nonnull NSNumber *)reactTag traffic:(BOOL)traffic) { 277 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 278 | RNYMView *view = (RNYMView *)viewRegistry[reactTag]; 279 | 280 | if (!view || ![view isKindOfClass:[RNYMView class]]) { 281 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 282 | return; 283 | } 284 | 285 | [view setTrafficVisible:traffic]; 286 | }]; 287 | } 288 | 289 | RCT_EXPORT_METHOD(getScreenPoints:(nonnull NSNumber *)reactTag json:(id)json _id:(NSString *_Nonnull)_id) { 290 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 291 | RNYMView *view = (RNYMView *)viewRegistry[reactTag]; 292 | 293 | if (!view || ![view isKindOfClass:[RNYMView class]]) { 294 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 295 | return; 296 | } 297 | 298 | NSArray *mapPoints = [RCTConvert Points:json]; 299 | [view emitWorldToScreenPoint:mapPoints withId:_id]; 300 | }]; 301 | } 302 | 303 | RCT_EXPORT_METHOD(getWorldPoints:(nonnull NSNumber *)reactTag json:(id)json _id:(NSString *_Nonnull)_id) { 304 | [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, NSDictionary *viewRegistry) { 305 | RNYMView *view = (RNYMView *)viewRegistry[reactTag]; 306 | 307 | if (!view || ![view isKindOfClass:[RNYMView class]]) { 308 | RCTLogError(@"Cannot find NativeView with tag #%@", reactTag); 309 | return; 310 | } 311 | 312 | NSArray *screenPoints = [RCTConvert ScreenPoints:json]; 313 | [view emitScreenToWorldPoint:screenPoints withId:_id]; 314 | }]; 315 | } 316 | 317 | @end 318 | --------------------------------------------------------------------------------