एक्सटेंशन मॉड्यूल बनाना
हीटमैप, GeoJSON लेयर और मार्कर क्लस्टरिंग ये सभी Core को बदले बिना बाहर से जोड़े गए अलग-अलग पैकेज हैं। एक ही इंटरफेस सार्वजनिक है, इसलिए तीसरे पक्ष भी उसी तरीके से अपने एक्सटेंशन वितरित कर सकते हैं। यह पृष्ठ उस चरण-दर-चरण प्रक्रिया के बारे में है।
00 · एक्सटेंशन मॉड्यूल का रूप
एक्सटेंशन मॉड्यूल एक स्वतंत्र पैकेज है जो केवल कोर मॉड्यूल पर निर्भर करता है। यह प्रोवाइडर पैकेज (react-for-*, android-for-*, ios-for-*) पर निर्भर नहीं करता है। यदि इसका पालन किया जाता है, तो उपयोगकर्ता द्वारा किसी भी मैप प्रोवाइडर को चुने जाने पर भी आपका एक्सटेंशन काम करेगा।
केवल कोर के सार्वजनिक प्रकारों (स्थिति, कलेक्टर, कंट्रोलर कॉन्ट्रैक्ट, टाइल सर्वर, सर्विस रजिस्ट्री) का उपयोग करें।
प्रोवाइडर के पैकेज को import करें। कंट्रोलर को अन्य प्रकार में कास्ट करके आंतरिक फ़ील्ड देखें।
शिप किए गए 3 पैकेज संदर्भ कार्यान्वयन के रूप में उपलब्ध हैं। जो चीज़ आप बनाना चाहते हैं उसके समान को पढ़ना सबसे आसान तरीका है।
heatmap रास्टर टाइल खुद खींचता है · टाइल सर्वर + RasterLayer geojson-layer बहुत सारे वेक्टर को टाइल बनाता है · टाइल सर्वर + RasterLayer marker-clustering मार्कर बदलकर वापस देता है · MarkerState + सर्विस रजिस्ट्री
01 · पैकेज बनाना
निर्भरता केवल कोर तक सीमित रखें। प्रोवाइडर उपयोगकर्ता के ऐप द्वारा चुना जाता है, इसलिए आपके पैकेज से यह दिखाई नहीं देता है।
dependencies {
implementation("com.mapconductor:core")
implementation("com.mapconductor:compose") // अगर Composable बाहर देना है
// android-for-* पर निर्भर न रहें
}.package(url: "https://github.com/MapConductor/ios-sdk-core", from: "1.3.1"),
.target(
name: "MyMapExtension",
dependencies: [.product(name: "MapConductorCore", package: "ios-sdk-core")]
// ios-for-* पर निर्भर न रहें
){
"dependencies": {
"@mapconductor/js-sdk-core": "^0.1.1",
"@mapconductor/js-sdk-react": "^0.1.1"
},
"peerDependencies": { "react": "^18.0.0 || ^19.0.0" }
}02 · स्थिति परिभाषित करना
उपयोगकर्ता स्टेट ऑब्जेक्ट के साथ इंटरैक्ट करते हैं। यदि आप कोर के स्टेट प्रकार (MarkerState आदि) के समान कन्वेंशन का पालन करते हैं, तो उपयोगकर्ताओं को कुछ और याद रखने की आवश्यकता नहीं होगी। मान बदलने की सूचना देने की एक विधि (फिंगरप्रिंट) शामिल करने से, आप इसे कोर कलेक्टर पर लोड करके इनक्रीमेंटल अपडेट प्राप्त कर सकते हैं।
OverlayCollector id → स्टेट का मैप; जोड़ना/हटाना/अंतर और बदलाव की सूचना एक जगह ComponentState id रखने वाले स्टेट का अनुबंध (Android) fingerPrint() तुलना के लिए मान, जिसमें सिर्फ़ वही है जो चित्रण पर असर डालता है
03 · रेंडरिंग एक्सिट चुनना
यह सबसे महत्वपूर्ण डिज़ाइन निर्णय है। प्रत्येक प्रोवाइडर के लिए रेंडरिंग कोड लिखने के बजाय, इसे कोर द्वारा पहले से प्रदान किए गए एक्सिट में भेजें। दो एक्सिट हैं।
खुद से टाइल बनाएं
स्थानीय टाइल सर्वर पर रेंडरिंग फ़ंक्शन पंजीकृत करें और उस URL टेम्पलेट की ओर इशारा करने वाला एक RasterLayer रखें। हीटमैप और GeoJSON लेयर ऐसे ही हैं। आइटम की संख्या जितनी अधिक होगी, उतना ही फायदेमंद है, और किसी भी प्रोवाइडर पर दिखावट पूरी तरह मेल खाता है।
मार्कर या आकृतियों के रूप में लौटाएं
इनपुट को प्रोसेस करके MarkerState / PolygonState आदि में बदलें और कलेक्टर में लिखें। उसके बाद प्रोवाइडर का सामान्य रेंडरिंग पाथ इसे संभाल लेता है। क्लस्टरिंग ऐसा ही करती है। क्लिक और ड्रैग वैसे ही काम करते हैं।
val tileServer = TileServerRegistry.get()
val renderer = MyTileRenderer(tileSize = 256) // एक टाइल खींचने वाला फ़ंक्शन रखता है
DisposableEffect(groupId) {
tileServer.register(groupId, renderer)
onDispose { tileServer.unregister(groupId) }
}
val layer = remember {
RasterLayerState(
id = "my-ext-$groupId",
source = RasterLayerSource.UrlTemplate(
template = tileServer.urlTemplate(groupId, renderer.tileSize),
tileSize = renderer.tileSize,
scheme = TileScheme.XYZ,
),
)
}
RasterLayer(layer)public struct MyOverlay: MapOverlayItemProtocol, View {
let state: MyOverlayState
public func append(to content: inout MapViewContent) {
// ios-heatmap के HeatmapOverlay की तरह ठीक उसी तरह जोड़ा जाता है
content.rasterLayers.append(RasterLayer(state: state.rasterLayerState))
}
}import { createRasterLayerState, TileServerRegistry, TileScheme } from '@mapconductor/js-sdk-core';
import { RasterLayer } from '@mapconductor/js-sdk-react';
const tileServer = TileServerRegistry.get();
useEffect(() => {
tileServer.register(groupId, renderer); // renderer एक टाइल खींचता है
return () => { tileServer.unregister(groupId); };
}, [groupId, tileServer, renderer]);
const state = useRef(createRasterLayerState({
id: `my-ext-${groupId}`,
source: {
kind: 'urlTemplate',
template: tileServer.urlTemplate(groupId, renderer.tileSize),
tileSize: renderer.tileSize,
scheme: TileScheme.XYZ,
},
})).current;
return <RasterLayer state={state} />;04 · कैमरा और सफाई
ज़ूम या पैन का अनुसरण करना चाहते हैं, तो मैप नियंत्रक की घटनाओं को सीधे छेड़छाड़ नहीं करनी चाहिए। इससे सिंगल स्लॉट श्रोता के लिए प्रतिस्पर्धा होगी, और दो एक्सटेंशन लगाने वाले उपयोगकर्ता के वातावरण में यह टूट जाएगा। ओवरले नियंत्रक के रूप में पंजीकृत करें, तो अन्य ओवरले के समान मार्ग से कैमरा परिवर्तन प्राप्त होगा।
class MyCameraController(
private val renderer: MyTileRenderer,
) : OverlayControllerInterface<Unit, Unit>, OnCameraChangeReceiverInterface {
override val zIndex: Int = 0
override suspend fun add(data: List<Unit>) {}
override suspend fun update(state: Unit) {}
override suspend fun clear() {}
override fun find(position: GeoPointInterface): Unit? = null
override suspend fun onCameraChanged(mapCameraPosition: MapCameraPosition) {
renderer.updateCameraZoom(mapCameraPosition.zoom)
}
override fun destroy() {}
}
// रजिस्टर करें
val mapController = LocalMapViewController.current
DisposableEffect(mapController, cameraController) {
mapController.registerOverlayController(cameraController)
onDispose { cameraController.destroy() }
}public final class MyCameraController: OverlayControllerProtocol {
public typealias StateType = Void
public typealias EntityType = Void
public typealias EventType = Void
public let zIndex: Int = 0
public var clickListener: ((Void) -> Void)?
public func add(data: [Void]) async {}
public func update(state: Void) async {}
public func clear() async {}
public func find(position: GeoPointProtocol) -> Void? { nil }
public func onCameraChanged(mapCameraPosition: MapCameraPosition) async {
renderer.updateCameraZoom(mapCameraPosition.zoom)
}
public func destroy() {}
}public func append(to content: inout MapViewContent) {
// iOS पर मैप की सामग्री व्यू पदानुक्रम नहीं, MapViewContent नाम का एक मान है, इसलिए
// कंट्रोलर तक MapServiceRegistryScope के ज़रिए पहुँचा जाता है
MapServiceRegistryScope.current
.get(OverlayControllerRegistryKey.self)?
.register(cameraController)
content.rasterLayers.append(RasterLayer(state: rasterLayerState))
}// कैमरा पढ़ने का सही रास्ता state है; दृश्य क्षेत्र भी यहीं से आता है:
// const camera = mapViewState.cameraPosition;
// const bounds = camera.visibleRegion?.bounds;
// बदलाव देखने के लिए onCameraMove / onCameraMoveEnd लें, या नीचे की तरह
// ओवरले कंट्रोलर रजिस्टर करके onCameraChanged लें।
export class MyCameraController implements OverlayController<void, void, void> {
readonly zIndex = 0;
clickListener: ((event: void) => void) | null = null;
constructor(private readonly renderer: MyTileRenderer) {}
add(): Promise<void> { return Promise.resolve(); }
update(): Promise<void> { return Promise.resolve(); }
clear(): Promise<void> { return Promise.resolve(); }
find(): void | null { return null; }
onCameraChanged(camera: MapCameraPosition): void {
this.renderer.updateCameraZoom(camera.zoom);
}
destroy(): void {}
}
// रजिस्टर करें
const { controller } = useContext(MapContext) ?? {};
useEffect(() => {
if (!controller) return;
controller.registerOverlayController?.(cameraController);
return () => { controller.unregisterOverlayController?.(cameraController); };
}, [controller, cameraController]);05 · कार्यक्षमता को हस्तांतरित करना
अब तक के इन 4 में पर्याप्त न होने पर ही उपयोग करें। जब प्रदाता के द्वारा ही बनाया जा सकने वाला कुछ आवश्यक हो, तो MapServiceRegistry के माध्यम से इसे प्राप्त करें। टाइप की गई कुंजी से पंजीकरण/प्राप्ति करने से, कास्टिंग की आवश्यकता नहीं होती है।
एक व्यावहारिक उदाहरण मार्कर क्लस्टरिंग है। देशी मार्कर प्रकार प्रदाता के अनुसार अलग होते हैं, इसलिए क्लस्टरिंग स्वयं रेंडरर नहीं बना सकता। इसलिए प्रदाता द्वारा पंजीकृत MarkerRenderingSupport को प्राप्त करें, और वहां से रेंडरर बनवाएं।
// कुंजी को singleton object के रूप में परिभाषित करें object MyCapabilityKey : MapServiceKey<MyCapability> // निकालें। रजिस्टर न हो तो null मिलता है, इसलिए तय करें कि सुविधा छोड़कर चलना है या रुकना val services = LocalMapServiceRegistry.current val capability = services.get(MyCapabilityKey) ?: return
// हर मैप के लिए एक रजिस्ट्री, जिसे state रखता है (react और ios जैसा ही)
state.serviceRegistry.put(MyCapabilityKey, myCapability)
// हटते समय clear() नहीं, remove() — ताकि दूसरी capability साथ में न गिरें
DisposableEffect(state) {
onDispose { state.serviceRegistry.remove(MyCapabilityKey) }
}// कुंजी को MapServiceKey के अनुरूप टाइप के रूप में परिभाषित करें
public enum MyCapabilityKey: MapServiceKey {
public typealias Value = any MyCapability
}
// रजिस्ट्री सिर्फ़ तब दिखती है जब content बनाया जा रहा हो
guard let capability = MapServiceRegistryScope.current.get(MyCapabilityKey.self) else { return }import { createMapServiceKey } from '@mapconductor/js-sdk-core';
import { useMapServiceRegistry } from '@mapconductor/js-sdk-react';
export const MyCapabilityKey = createMapServiceKey<MyCapability>();
const services = useMapServiceRegistry();
const capability = services.get(MyCapabilityKey);
if (!capability) return null; // जिन प्रोवाइडर पर रजिस्टर नहीं है, वहाँ चुपचाप बंद कर दें// हर मैप के लिए एक रजिस्ट्री, जिसे state रखता है state.serviceRegistry.put(MyCapabilityKey, myCapability); // अनमाउंट पर clear() नहीं, remove() — ताकि दूसरी capability साथ में न गिरें state.serviceRegistry.remove(MyCapabilityKey);
पालन करने योग्य
इन 4 का पालन करते हुए, उपयोगकर्ता प्रदाता को बदलने पर भी या दूसरे एक्सटेंशन के साथ एक साथ उपयोग करने पर भी यह टूटेगा नहीं।
1 · प्रोवाइडर के पैकेज को import न करें
ज़रूरत पड़ने पर, वह एक ऐसी सुविधा है जिसे आपको सर्विस रजिस्ट्री से प्राप्त करना चाहिए।
2 · कंट्रोलर को कास्ट करके अंदर न झांकें
react-heatmap पहले ऐसा करता था और सिंगल-स्लॉट कैमरा लिसनर को सेव और रिस्टोर करता था। दो एक्सटेंशन लगने पर वे एक-दूसरे को ओवरराइट कर देंगे। इसे सार्वजनिक registerOverlayController से बदल दिया गया है।
3 · पंजीकृत चीज़ों को हमेशा रद्द करें
ओवरले कंट्रोलर, टाइल सर्वर का ग्रुप, सर्विस रजिस्ट्री की कुंजी। remove() केवल एक आइटम वापस लेता है।
4 · अगर पंजीकृत नहीं है तो शांत से अक्षम करें
उन प्रोवाइडर पर जिनमें आवश्यक capability नहीं है, बिना अपवाद फेंके कुछ भी न बनाएं। उपयोगकर्ता अन्य हिस्सों को चलाते रह सकते हैं।