Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package com.pkg.membermap;
- import android.app.Activity;
- import android.content.Context;
- import android.graphics.Bitmap;
- import android.graphics.drawable.BitmapDrawable;
- import android.os.Build;
- import android.os.Handler;
- import android.os.Looper;
- import android.util.DisplayMetrics;
- import android.view.LayoutInflater;
- import android.view.View;
- import com.facebook.Profile;
- import com.google.android.gms.maps.MapsInitializer;
- import com.google.android.gms.maps.model.BitmapDescriptor;
- import com.google.maps.android.clustering.Cluster;
- import com.google.maps.android.clustering.ClusterItem;
- import com.google.maps.android.clustering.ClusterManager;
- import com.google.maps.android.clustering.algo.NonHierarchicalViewBasedAlgorithm;
- import com.google.maps.android.clustering.view.DefaultClusterRenderer;
- import androidx.annotation.NonNull;
- import androidx.annotation.Nullable;
- import com.facebook.react.bridge.Arguments;
- import com.facebook.react.bridge.ReactContext;
- import com.facebook.react.bridge.ReactMethod;
- import com.facebook.react.bridge.ReadableArray;
- import com.facebook.react.bridge.ReadableMap;
- import com.facebook.react.bridge.WritableMap;
- import com.facebook.react.common.MapBuilder;
- import com.facebook.react.uimanager.annotations.ReactProp;
- import com.facebook.react.uimanager.events.RCTEventEmitter;
- import com.google.android.gms.maps.MapView;
- import com.google.android.gms.maps.CameraUpdateFactory;
- import com.google.android.gms.maps.GoogleMap;
- import com.facebook.react.uimanager.SimpleViewManager;
- import com.facebook.react.uimanager.ThemedReactContext;
- import com.facebook.react.bridge.ReactApplicationContext;
- import com.google.android.gms.maps.model.BitmapDescriptorFactory;
- import com.google.android.gms.maps.model.LatLng;
- import com.google.android.gms.maps.model.LatLngBounds;
- import com.google.android.gms.maps.model.Marker;
- import com.google.android.gms.maps.model.MarkerOptions;
- import com.google.maps.android.ui.IconGenerator;
- import com.pkg.R;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Map;
- import java.util.Set;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.ThreadPoolExecutor;
- import java.util.concurrent.TimeUnit;
- public class MemberMap extends SimpleViewManager<MapView> implements
- com.google.android.gms.maps.OnMapReadyCallback,
- GoogleMap.OnMapClickListener,
- GoogleMap.OnCameraIdleListener,
- GoogleMap.OnMapLoadedCallback,
- ClusterManager.OnClusterItemClickListener {
- public static final String REACT_CLASS = "RCTMemberMap";
- private final String ANIMATE_MAP_TO_LOCATION = "ANIMATE_MAP_TO_LOCATION";
- ReactApplicationContext reactContext;
- ThemedReactContext context;
- private MapView mapView;
- private LatLngBounds boundsToMove;
- private GoogleMap map;
- private ReadableMap initialRegion;
- private ReadableArray pins;
- private boolean mapReady = false;
- private boolean renderPinsOnLoad = false;
- private ThreadPoolExecutor executor;
- ExecutorService renderExecutor = Executors.newSingleThreadExecutor();
- Handler handler = new Handler(Looper.getMainLooper());
- private volatile int renderCount = 0;
- private int coreCount = 1;
- private Activity activity = null;
- private ClusterManager<ProfileClusterItem> clusterManager;
- private DefaultClusterRenderer<ProfileClusterItem> clusterRender;
- private String selectedId = null;
- private ProfileClusterItem selectedItem = null;
- private float oldSelectedZIndex = Float.MIN_VALUE;
- private HashSet<String> visitedIds = new HashSet<>();
- public MemberMap(ReactApplicationContext context) {
- reactContext = context;
- coreCount = ParallelProcessing.getNumberOfCores();
- }
- @Override
- public Map getExportedCustomBubblingEventTypeConstants() {
- return MapBuilder.builder()
- .put(
- "pinClick",
- MapBuilder.of(
- "phasedRegistrationNames",
- MapBuilder.of("bubbled", "onPinClickEvent")))
- .put(
- "mapClick",
- MapBuilder.of(
- "phasedRegistrationNames",
- MapBuilder.of("bubbled", "onMapClickEvent")))
- .put(
- "cameraIdle",
- MapBuilder.of(
- "phasedRegistrationNames",
- MapBuilder.of("bubbled", "onCameraIdleEvent")))
- .build();
- }
- @Override
- public String getName() {
- return REACT_CLASS;
- }
- @Override
- protected MapView createViewInstance(ThemedReactContext context) {
- MapsInitializer.initialize(reactContext.getApplicationContext());
- mapView = new MapView(context);
- this.context = context;
- mapView.onCreate(null);
- mapView.getMapAsync(this);
- mapView.onResume();
- return mapView;
- }
- @Override
- public void onMapReady(GoogleMap googleMap) {
- map = googleMap;
- googleMap.clear();
- if (initialRegion != null) {
- double lat = initialRegion.getDouble("latitude");
- double lng = initialRegion.getDouble("longitude");
- double latDelta = initialRegion.getDouble("latitudeDelta");
- double lngDelta = initialRegion.getDouble("longitudeDelta");
- LatLngBounds bounds = new LatLngBounds(new LatLng(lat - latDelta, lng - lngDelta), new LatLng(lat + latDelta, lng + lngDelta));
- if (mapView.getHeight() <= 0 || mapView.getWidth() <= 0) {
- // in this case, our map has not been laid out yet, so we save the bounds in a local
- // variable, and make a guess of zoomLevel 10. Not to worry, though: as soon as layout
- // occurs, we will move the camera to the saved bounds. Note that if we tried to move
- // to the bounds now, it would trigger an exception.
- map.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(lat, lng), 10));
- boundsToMove = bounds;
- } else {
- map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0));
- boundsToMove = null;
- }
- }
- googleMap.setOnMapLoadedCallback(this);
- googleMap.getUiSettings().setCompassEnabled(false);
- googleMap.setMinZoomPreference(5.5f);
- googleMap.setOnMapClickListener(this);
- googleMap.setOnCameraIdleListener(this);
- }
- @ReactProp(name = "initialMapRegion")
- public void setInitialMapRegion(MapView view, ReadableMap geo) {
- if (geo != null) {
- initialRegion = geo;
- }
- }
- private void renderPins() {
- try {
- renderExecutor.shutdownNow();
- renderExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
- } catch (Exception e) {
- }
- renderExecutor = Executors.newSingleThreadExecutor();
- renderExecutor.execute(() -> {
- if (executor != null) {
- executor.shutdownNow();
- try {
- executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
- } catch (Exception e) {
- }
- }
- renderCount++;
- int thisRenderCount = renderCount;
- ReadableArray thisPins = pins;
- int numCoresT = coreCount;
- final int numCores = numCoresT;
- activity = reactContext.getCurrentActivity();
- executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(numCores);
- int numPins = thisPins.size();
- if (numPins < 1)
- return;
- Set<String> pinsHashSet = Collections.synchronizedSet(new HashSet<String>());
- Collection<ProfileClusterItem> items = clusterManager.getAlgorithm().getItems();
- HashSet<String> displayedPinsHashSet = new HashSet<>(items.size());
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- items.parallelStream()
- .forEach(item -> displayedPinsHashSet.add(item.getProfileId()));
- } else {
- ExecutorService executor = Executors.newFixedThreadPool(numCoresT);
- List<ProfileClusterItem> itemList = new ArrayList<>(items);
- int segmentSize = (int) Math.ceil((double) itemList.size() / numCoresT);
- // Split the list into segments and submit tasks to the executor
- for (int i = 0; i < numCoresT; i++) {
- int start = i * segmentSize;
- int end = Math.min((i + 1) * segmentSize, itemList.size());
- List<ProfileClusterItem> segment = itemList.subList(start, end);
- // Submit a task to process each segment
- executor.submit(() -> {
- for (ProfileClusterItem item : segment) {
- displayedPinsHashSet.add(item.getProfileId());
- }
- });
- }
- // Shutdown the executor and wait for all tasks to complete
- executor.shutdown();
- try {
- executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
- } catch (InterruptedException e) {
- // Handle interruption
- }
- }
- List<ProfileClusterItem> clusterItems = Collections.synchronizedList(new ArrayList<ProfileClusterItem>(numPins));
- for (int t = 0; t < numCores; t++) {
- final int numExec = t;
- executor.execute(() -> {
- int minPin = (int) (Math.floor(numPins / numCores) * numExec);
- int maxPin = 0;
- if (numExec == numCores - 1) {
- maxPin = numPins;
- } else {
- maxPin = (int) (Math.floor(numPins / numCores) * (numExec + 1)) + 1;
- }
- for (int i = minPin; i < maxPin; i++) {
- if (renderCount != thisRenderCount) {
- return;
- }
- try {
- ReadableArray pinarr = thisPins.getArray(i);
- String type = pinarr.getString(0);
- String profileId = pinarr.getString(1);
- if (!displayedPinsHashSet.contains(profileId)) {
- if (type.equals("h")) {
- ReadableArray pinloc = pinarr.getArray(2);
- LatLng point = new LatLng(pinloc.getDouble(0), pinloc.getDouble(1));
- if (renderCount == thisRenderCount && activity != null && point != null) {
- String country = pinarr.getString(4);
- int price = pinarr.getInt(3);
- boolean selectedPin = selectedId != null && selectedId.equals(profileId);
- boolean wasVisited = visitedIds.contains(profileId);
- ProfileClusterItem item = new ProfileClusterItem(point, profileId, "h", price, country, HaveIcon.getIcon(mapView, price, country, selectedPin ? HaveIcon.HaveIconState.ACTIVE : (wasVisited ? HaveIcon.HaveIconState.VISITED : HaveIcon.HaveIconState.UNVISITED)));
- clusterItems.add(item);
- }
- } else if (type.equals("n")) {
- ReadableArray pinlocs = pinarr.getArray(2);
- for (int j = 0; j < pinlocs.size(); j++) {
- ReadableArray pinloc = pinlocs.getArray(j);
- LatLng point = new LatLng(pinloc.getDouble(0), pinloc.getDouble(1));
- if (renderCount == thisRenderCount && activity != null && point != null) {
- //final CircleOptions circleOptions = new CircleOptions().center(point).radius(pinloc.getDouble(2)).strokeWidth(1f).strokeColor(Color.parseColor("#ff65c5")).fillColor(Color.parseColor("#5566CDFF"));
- ProfileClusterItem item = new ProfileClusterItem(point, profileId, "n", NeedIcon.getIcon(mapView));
- clusterItems.add(item);
- }
- }
- }
- }
- synchronized (pinsHashSet) {
- pinsHashSet.add(profileId);
- }
- } catch (Exception e) {
- }
- }
- });
- }
- try {
- executor.shutdown();
- executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (thisRenderCount != renderCount) {
- return;
- }
- synchronized (clusterItems) {
- clusterManager.addItems(clusterItems);
- items = clusterManager.getAlgorithm().getItems();
- ArrayList<ProfileClusterItem> toRemove = new ArrayList<>(numPins);
- for (ProfileClusterItem item : items) {
- String profileId = item.getProfileId();
- if (!pinsHashSet.contains(profileId)) {
- toRemove.add(item);
- }
- }
- clusterManager.removeItems(toRemove);
- }
- handler.post(() -> {
- if (thisRenderCount == renderCount) {
- clusterManager.cluster();
- }
- });
- });
- }
- @ReactProp(name = "pins")
- public void setPins(MapView view, ReadableArray pins) {
- if (pins != null) {
- this.pins = pins;
- if (mapReady) {
- this.renderPins();
- } else {
- this.renderPinsOnLoad = true;
- }
- }
- }
- @Override
- public void receiveCommand(MapView root, String commandId, @Nullable ReadableArray args) {
- // This will be called whenever a command is sent from react-native.
- switch (commandId) {
- case ANIMATE_MAP_TO_LOCATION:
- this.animateMapToLocation(args.getMap(0));
- break;
- }
- }
- @ReactMethod
- public void animateMapToLocation(ReadableMap geo) {
- if (geo != null) {
- this.boundsToMove = null;
- ReadableMap loc = geo.getMap("viewport");
- map.animateCamera(CameraUpdateFactory.newLatLngBounds(new LatLngBounds(new LatLng(loc.getMap("southwest").getDouble("lat"), loc.getMap("southwest").getDouble("lng")), new LatLng(loc.getMap("northeast").getDouble("lat"), loc.getMap("northeast").getDouble("lng"))), 0));
- }
- }
- @Override
- public void onMapClick(@NonNull LatLng latLng) {
- WritableMap event = Arguments.createMap();
- event.putDouble("latitude", latLng.latitude);
- event.putDouble("longitude", latLng.longitude);
- ReactContext reactContext = (ReactContext) mapView.getContext();
- reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
- mapView.getId(),
- "mapClick",
- event
- );
- selectedId = null;
- if (selectedItem != null) {
- Marker m = clusterRender.getMarker(selectedItem);
- if (m != null) {
- m.setIcon(HaveIcon.getIcon(mapView, selectedItem.getPrice(), selectedItem.getCountry(), HaveIcon.HaveIconState.VISITED));
- if (oldSelectedZIndex != Float.MIN_VALUE) {
- m.setZIndex(oldSelectedZIndex);
- }
- }
- oldSelectedZIndex = Float.MIN_VALUE;
- }
- }
- @Override
- public void onCameraIdle() {
- WritableMap event = Arguments.createMap();
- LatLngBounds bounds = map.getProjection().getVisibleRegion().latLngBounds;
- WritableMap ne = Arguments.createMap();
- ne.putDouble("latitude", bounds.northeast.latitude);
- ne.putDouble("longitude", bounds.northeast.longitude);
- WritableMap sw = Arguments.createMap();
- sw.putDouble("latitude", bounds.southwest.latitude);
- sw.putDouble("longitude", bounds.southwest.longitude);
- event.putMap("northeast", ne);
- event.putMap("southwest", sw);
- ReactContext reactContext = (ReactContext) mapView.getContext();
- reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
- mapView.getId(),
- "cameraIdle",
- event);
- if (clusterManager != null) {
- clusterManager.onCameraIdle();
- }
- }
- @Override
- public void onMapLoaded() {
- if (boundsToMove != null) {
- map.animateCamera(CameraUpdateFactory.newLatLngBounds(boundsToMove, 0));
- boundsToMove = null;
- }
- clusterManager = new ClusterManager<ProfileClusterItem>(mapView.getContext(), map);
- IconGenerator gen = new IconGenerator(mapView.getContext());
- gen.setBackground(mapView.getContext().getDrawable(R.drawable.map_cluster));
- LayoutInflater myInflater = (LayoutInflater) mapView.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View activityView = myInflater.inflate(R.layout.cluster_view, null, false);
- gen.setContentView(activityView);
- clusterRender = new DefaultClusterRenderer<ProfileClusterItem>(mapView.getContext(), map, clusterManager) {
- @Override
- protected void onBeforeClusterRendered(Cluster<ProfileClusterItem> cluster, MarkerOptions markerOptions) {
- int clusterNum = cluster.getSize();
- markerOptions.icon(BitmapDescriptorFactory.fromBitmap(gen.makeIcon(Integer.toString(clusterNum))));
- }
- @Override
- protected void onClusterUpdated(Cluster<ProfileClusterItem> cluster, Marker marker) {
- int clusterNum = cluster.getSize();
- marker.setIcon(BitmapDescriptorFactory.fromBitmap(gen.makeIcon(Integer.toString(clusterNum))));
- }
- @Override
- protected void onBeforeClusterItemRendered(ProfileClusterItem item, MarkerOptions markerOptions) {
- markerOptions.icon(item.getIcon());
- markerOptions.snippet(item.getSnippet());
- markerOptions.title(item.getTitle());
- }
- };
- clusterRender.setMinClusterSize(5);
- DisplayMetrics displayMetrics = mapView.getContext().getResources().getDisplayMetrics();
- clusterManager.setAlgorithm(new NonHierarchicalViewBasedAlgorithm<ProfileClusterItem>(displayMetrics.widthPixels, displayMetrics.heightPixels));
- clusterManager.setRenderer(clusterRender);
- clusterManager.setOnClusterItemClickListener(this);
- mapReady = true;
- if (renderPinsOnLoad) {
- this.renderPins();
- }
- }
- @Override
- public boolean onClusterItemClick(ClusterItem item) {
- if (item == null) {
- return true;
- }
- map.animateCamera(CameraUpdateFactory.newLatLng(item.getPosition()), 300, null);
- String profileId = (String) ((ProfileClusterItem) item).getProfileId();
- WritableMap event = Arguments.createMap();
- event.putString("profileId", profileId);
- if (selectedItem != null && selectedItem != item) {
- Marker m = clusterRender.getMarker(selectedItem);
- if (m != null) {
- m.setIcon(HaveIcon.getIcon(mapView, ((ProfileClusterItem) selectedItem).getPrice(), ((ProfileClusterItem) selectedItem).getCountry(), HaveIcon.HaveIconState.VISITED));
- if (oldSelectedZIndex != -1) {
- m.setZIndex(oldSelectedZIndex);
- }
- }
- }
- if (((ProfileClusterItem) item).getHaveOrNeed().equals("h")) {
- Marker marker = clusterRender.getMarker((ProfileClusterItem) item);
- if (marker != null) {
- marker.setIcon(HaveIcon.getIcon(mapView, ((ProfileClusterItem) item).getPrice(), ((ProfileClusterItem) item).getCountry(), HaveIcon.HaveIconState.ACTIVE));
- oldSelectedZIndex = marker.getZIndex();
- selectedItem = (ProfileClusterItem) item;
- marker.setZIndex(Float.MAX_VALUE);
- }
- selectedId = profileId;
- visitedIds.add(selectedId);
- } else {
- selectedItem = null;
- selectedId = null;
- }
- ReactContext reactContext = (ReactContext) mapView.getContext();
- reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
- mapView.getId(),
- "pinClick",
- event);
- return true;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement