Advertisement
Guest User

Java Implementation

a guest
Oct 25th, 2024
520
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 21.67 KB | None | 0 0
  1. package com.pkg.membermap;
  2.  
  3. import android.app.Activity;
  4. import android.content.Context;
  5. import android.graphics.Bitmap;
  6. import android.graphics.drawable.BitmapDrawable;
  7. import android.os.Build;
  8. import android.os.Handler;
  9. import android.os.Looper;
  10. import android.util.DisplayMetrics;
  11. import android.view.LayoutInflater;
  12. import android.view.View;
  13.  
  14. import com.facebook.Profile;
  15. import com.google.android.gms.maps.MapsInitializer;
  16. import com.google.android.gms.maps.model.BitmapDescriptor;
  17. import com.google.maps.android.clustering.Cluster;
  18. import com.google.maps.android.clustering.ClusterItem;
  19. import com.google.maps.android.clustering.ClusterManager;
  20. import com.google.maps.android.clustering.algo.NonHierarchicalViewBasedAlgorithm;
  21. import com.google.maps.android.clustering.view.DefaultClusterRenderer;
  22.  
  23. import androidx.annotation.NonNull;
  24. import androidx.annotation.Nullable;
  25.  
  26. import com.facebook.react.bridge.Arguments;
  27. import com.facebook.react.bridge.ReactContext;
  28. import com.facebook.react.bridge.ReactMethod;
  29. import com.facebook.react.bridge.ReadableArray;
  30. import com.facebook.react.bridge.ReadableMap;
  31. import com.facebook.react.bridge.WritableMap;
  32. import com.facebook.react.common.MapBuilder;
  33. import com.facebook.react.uimanager.annotations.ReactProp;
  34. import com.facebook.react.uimanager.events.RCTEventEmitter;
  35. import com.google.android.gms.maps.MapView;
  36. import com.google.android.gms.maps.CameraUpdateFactory;
  37. import com.google.android.gms.maps.GoogleMap;
  38. import com.facebook.react.uimanager.SimpleViewManager;
  39. import com.facebook.react.uimanager.ThemedReactContext;
  40. import com.facebook.react.bridge.ReactApplicationContext;
  41. import com.google.android.gms.maps.model.BitmapDescriptorFactory;
  42. import com.google.android.gms.maps.model.LatLng;
  43. import com.google.android.gms.maps.model.LatLngBounds;
  44. import com.google.android.gms.maps.model.Marker;
  45. import com.google.android.gms.maps.model.MarkerOptions;
  46. import com.google.maps.android.ui.IconGenerator;
  47. import com.pkg.R;
  48.  
  49. import java.util.ArrayList;
  50. import java.util.Collection;
  51. import java.util.Collections;
  52. import java.util.HashSet;
  53. import java.util.Iterator;
  54. import java.util.List;
  55. import java.util.Map;
  56. import java.util.Set;
  57. import java.util.concurrent.ExecutorService;
  58. import java.util.concurrent.Executors;
  59. import java.util.concurrent.ThreadPoolExecutor;
  60. import java.util.concurrent.TimeUnit;
  61.  
  62. public class MemberMap extends SimpleViewManager<MapView> implements
  63.         com.google.android.gms.maps.OnMapReadyCallback,
  64.         GoogleMap.OnMapClickListener,
  65.         GoogleMap.OnCameraIdleListener,
  66.         GoogleMap.OnMapLoadedCallback,
  67.         ClusterManager.OnClusterItemClickListener {
  68.     public static final String REACT_CLASS = "RCTMemberMap";
  69.  
  70.  
  71.     private final String ANIMATE_MAP_TO_LOCATION = "ANIMATE_MAP_TO_LOCATION";
  72.  
  73.     ReactApplicationContext reactContext;
  74.     ThemedReactContext context;
  75.     private MapView mapView;
  76.     private LatLngBounds boundsToMove;
  77.     private GoogleMap map;
  78.     private ReadableMap initialRegion;
  79.     private ReadableArray pins;
  80.     private boolean mapReady = false;
  81.     private boolean renderPinsOnLoad = false;
  82.     private ThreadPoolExecutor executor;
  83.  
  84.     ExecutorService renderExecutor = Executors.newSingleThreadExecutor();
  85.  
  86.     Handler handler = new Handler(Looper.getMainLooper());
  87.  
  88.     private volatile int renderCount = 0;
  89.     private int coreCount = 1;
  90.     private Activity activity = null;
  91.     private ClusterManager<ProfileClusterItem> clusterManager;
  92.     private DefaultClusterRenderer<ProfileClusterItem> clusterRender;
  93.  
  94.     private String selectedId = null;
  95.  
  96.     private ProfileClusterItem selectedItem = null;
  97.  
  98.     private float oldSelectedZIndex = Float.MIN_VALUE;
  99.  
  100.     private HashSet<String> visitedIds = new HashSet<>();
  101.  
  102.  
  103.     public MemberMap(ReactApplicationContext context) {
  104.         reactContext = context;
  105.         coreCount = ParallelProcessing.getNumberOfCores();
  106.     }
  107.  
  108.     @Override
  109.     public Map getExportedCustomBubblingEventTypeConstants() {
  110.         return MapBuilder.builder()
  111.                 .put(
  112.                         "pinClick",
  113.                         MapBuilder.of(
  114.                                 "phasedRegistrationNames",
  115.                                 MapBuilder.of("bubbled", "onPinClickEvent")))
  116.                 .put(
  117.                         "mapClick",
  118.                         MapBuilder.of(
  119.                                 "phasedRegistrationNames",
  120.                                 MapBuilder.of("bubbled", "onMapClickEvent")))
  121.                 .put(
  122.                         "cameraIdle",
  123.                         MapBuilder.of(
  124.                                 "phasedRegistrationNames",
  125.                                 MapBuilder.of("bubbled", "onCameraIdleEvent")))
  126.                 .build();
  127.     }
  128.  
  129.     @Override
  130.     public String getName() {
  131.         return REACT_CLASS;
  132.     }
  133.  
  134.     @Override
  135.     protected MapView createViewInstance(ThemedReactContext context) {
  136.         MapsInitializer.initialize(reactContext.getApplicationContext());
  137.         mapView = new MapView(context);
  138.         this.context = context;
  139.         mapView.onCreate(null);
  140.         mapView.getMapAsync(this);
  141.  
  142.         mapView.onResume();
  143.  
  144.         return mapView;
  145.     }
  146.  
  147.     @Override
  148.     public void onMapReady(GoogleMap googleMap) {
  149.         map = googleMap;
  150.  
  151.         googleMap.clear();
  152.  
  153.  
  154.         if (initialRegion != null) {
  155.             double lat = initialRegion.getDouble("latitude");
  156.             double lng = initialRegion.getDouble("longitude");
  157.             double latDelta = initialRegion.getDouble("latitudeDelta");
  158.             double lngDelta = initialRegion.getDouble("longitudeDelta");
  159.             LatLngBounds bounds = new LatLngBounds(new LatLng(lat - latDelta, lng - lngDelta), new LatLng(lat + latDelta, lng + lngDelta));
  160.  
  161.             if (mapView.getHeight() <= 0 || mapView.getWidth() <= 0) {
  162.                 // in this case, our map has not been laid out yet, so we save the bounds in a local
  163.                 // variable, and make a guess of zoomLevel 10. Not to worry, though: as soon as layout
  164.                 // occurs, we will move the camera to the saved bounds. Note that if we tried to move
  165.                 // to the bounds now, it would trigger an exception.
  166.                 map.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(lat, lng), 10));
  167.                 boundsToMove = bounds;
  168.             } else {
  169.                 map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 0));
  170.                 boundsToMove = null;
  171.             }
  172.  
  173.         }
  174.  
  175.         googleMap.setOnMapLoadedCallback(this);
  176.         googleMap.getUiSettings().setCompassEnabled(false);
  177.         googleMap.setMinZoomPreference(5.5f);
  178.         googleMap.setOnMapClickListener(this);
  179.         googleMap.setOnCameraIdleListener(this);
  180.     }
  181.  
  182.     @ReactProp(name = "initialMapRegion")
  183.     public void setInitialMapRegion(MapView view, ReadableMap geo) {
  184.         if (geo != null) {
  185.             initialRegion = geo;
  186.         }
  187.     }
  188.  
  189.     private void renderPins() {
  190.         try {
  191.             renderExecutor.shutdownNow();
  192.             renderExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
  193.         } catch (Exception e) {
  194.         }
  195.  
  196.         renderExecutor = Executors.newSingleThreadExecutor();
  197.  
  198.         renderExecutor.execute(() -> {
  199.             if (executor != null) {
  200.                 executor.shutdownNow();
  201.                 try {
  202.                     executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
  203.                 } catch (Exception e) {
  204.  
  205.                 }
  206.             }
  207.  
  208.             renderCount++;
  209.             int thisRenderCount = renderCount;
  210.             ReadableArray thisPins = pins;
  211.  
  212.             int numCoresT = coreCount;
  213.  
  214.             final int numCores = numCoresT;
  215.             activity = reactContext.getCurrentActivity();
  216.  
  217.             executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(numCores);
  218.  
  219.  
  220.             int numPins = thisPins.size();
  221.  
  222.             if (numPins < 1)
  223.                 return;
  224.  
  225.             Set<String> pinsHashSet = Collections.synchronizedSet(new HashSet<String>());
  226.  
  227.             Collection<ProfileClusterItem> items = clusterManager.getAlgorithm().getItems();
  228.  
  229.             HashSet<String> displayedPinsHashSet = new HashSet<>(items.size());
  230.  
  231.             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
  232.                 items.parallelStream()
  233.                         .forEach(item -> displayedPinsHashSet.add(item.getProfileId()));
  234.             } else {
  235.                 ExecutorService executor = Executors.newFixedThreadPool(numCoresT);
  236.                 List<ProfileClusterItem> itemList = new ArrayList<>(items);
  237.                 int segmentSize = (int) Math.ceil((double) itemList.size() / numCoresT);
  238.  
  239. // Split the list into segments and submit tasks to the executor
  240.                 for (int i = 0; i < numCoresT; i++) {
  241.                     int start = i * segmentSize;
  242.                     int end = Math.min((i + 1) * segmentSize, itemList.size());
  243.                     List<ProfileClusterItem> segment = itemList.subList(start, end);
  244.  
  245.                     // Submit a task to process each segment
  246.                     executor.submit(() -> {
  247.                         for (ProfileClusterItem item : segment) {
  248.                             displayedPinsHashSet.add(item.getProfileId());
  249.                         }
  250.                     });
  251.                 }
  252.  
  253. // Shutdown the executor and wait for all tasks to complete
  254.                 executor.shutdown();
  255.                 try {
  256.                     executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
  257.                 } catch (InterruptedException e) {
  258.                     // Handle interruption
  259.                 }
  260.             }
  261.  
  262.             List<ProfileClusterItem> clusterItems = Collections.synchronizedList(new ArrayList<ProfileClusterItem>(numPins));
  263.  
  264.             for (int t = 0; t < numCores; t++) {
  265.                 final int numExec = t;
  266.  
  267.                 executor.execute(() -> {
  268.                     int minPin = (int) (Math.floor(numPins / numCores) * numExec);
  269.                     int maxPin = 0;
  270.  
  271.                     if (numExec == numCores - 1) {
  272.                         maxPin = numPins;
  273.                     } else {
  274.                         maxPin = (int) (Math.floor(numPins / numCores) * (numExec + 1)) + 1;
  275.                     }
  276.  
  277.  
  278.                     for (int i = minPin; i < maxPin; i++) {
  279.                         if (renderCount != thisRenderCount) {
  280.                             return;
  281.                         }
  282.  
  283.                         try {
  284.  
  285.                             ReadableArray pinarr = thisPins.getArray(i);
  286.  
  287.                             String type = pinarr.getString(0);
  288.                             String profileId = pinarr.getString(1);
  289.  
  290.                             if (!displayedPinsHashSet.contains(profileId)) {
  291.                                 if (type.equals("h")) {
  292.                                     ReadableArray pinloc = pinarr.getArray(2);
  293.                                     LatLng point = new LatLng(pinloc.getDouble(0), pinloc.getDouble(1));
  294.  
  295.  
  296.                                     if (renderCount == thisRenderCount && activity != null && point != null) {
  297.                                         String country = pinarr.getString(4);
  298.                                         int price = pinarr.getInt(3);
  299.                                         boolean selectedPin = selectedId != null && selectedId.equals(profileId);
  300.                                         boolean wasVisited = visitedIds.contains(profileId);
  301.                                         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)));
  302.                                         clusterItems.add(item);
  303.  
  304.                                     }
  305.                                 } else if (type.equals("n")) {
  306.                                     ReadableArray pinlocs = pinarr.getArray(2);
  307.  
  308.                                     for (int j = 0; j < pinlocs.size(); j++) {
  309.                                         ReadableArray pinloc = pinlocs.getArray(j);
  310.  
  311.  
  312.                                         LatLng point = new LatLng(pinloc.getDouble(0), pinloc.getDouble(1));
  313.  
  314.                                         if (renderCount == thisRenderCount && activity != null && point != null) {
  315.                                             //final CircleOptions circleOptions = new CircleOptions().center(point).radius(pinloc.getDouble(2)).strokeWidth(1f).strokeColor(Color.parseColor("#ff65c5")).fillColor(Color.parseColor("#5566CDFF"));
  316.                                             ProfileClusterItem item = new ProfileClusterItem(point, profileId, "n", NeedIcon.getIcon(mapView));
  317.                                             clusterItems.add(item);
  318.  
  319.                                         }
  320.  
  321.  
  322.                                     }
  323.                                 }
  324.                             }
  325.  
  326.                             synchronized (pinsHashSet) {
  327.                                 pinsHashSet.add(profileId);
  328.                             }
  329.  
  330.                         } catch (Exception e) {
  331.  
  332.                         }
  333.  
  334.  
  335.                     }
  336.                 });
  337.             }
  338.  
  339.             try {
  340.                 executor.shutdown();
  341.                 executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
  342.             } catch (InterruptedException e) {
  343.                 e.printStackTrace();
  344.             }
  345.  
  346.  
  347.             if (thisRenderCount != renderCount) {
  348.                 return;
  349.             }
  350.  
  351.             synchronized (clusterItems) {
  352.  
  353.                 clusterManager.addItems(clusterItems);
  354.  
  355.                 items = clusterManager.getAlgorithm().getItems();
  356.  
  357.                 ArrayList<ProfileClusterItem> toRemove = new ArrayList<>(numPins);
  358.  
  359.                 for (ProfileClusterItem item : items) {
  360.                     String profileId = item.getProfileId();
  361.                     if (!pinsHashSet.contains(profileId)) {
  362.                         toRemove.add(item);
  363.                     }
  364.                 }
  365.  
  366.                 clusterManager.removeItems(toRemove);
  367.  
  368.             }
  369.  
  370.             handler.post(() -> {
  371.                 if (thisRenderCount == renderCount) {
  372.                     clusterManager.cluster();
  373.                 }
  374.             });
  375.  
  376.         });
  377.  
  378.     }
  379.  
  380.     @ReactProp(name = "pins")
  381.     public void setPins(MapView view, ReadableArray pins) {
  382.         if (pins != null) {
  383.             this.pins = pins;
  384.  
  385.             if (mapReady) {
  386.                 this.renderPins();
  387.             } else {
  388.                 this.renderPinsOnLoad = true;
  389.             }
  390.         }
  391.     }
  392.  
  393.     @Override
  394.     public void receiveCommand(MapView root, String commandId, @Nullable ReadableArray args) {
  395.         // This will be called whenever a command is sent from react-native.
  396.         switch (commandId) {
  397.             case ANIMATE_MAP_TO_LOCATION:
  398.                 this.animateMapToLocation(args.getMap(0));
  399.                 break;
  400.         }
  401.     }
  402.  
  403.     @ReactMethod
  404.     public void animateMapToLocation(ReadableMap geo) {
  405.         if (geo != null) {
  406.             this.boundsToMove = null;
  407.             ReadableMap loc = geo.getMap("viewport");
  408.             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));
  409.         }
  410.     }
  411.  
  412.     @Override
  413.     public void onMapClick(@NonNull LatLng latLng) {
  414.         WritableMap event = Arguments.createMap();
  415.         event.putDouble("latitude", latLng.latitude);
  416.         event.putDouble("longitude", latLng.longitude);
  417.         ReactContext reactContext = (ReactContext) mapView.getContext();
  418.         reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
  419.                 mapView.getId(),
  420.                 "mapClick",
  421.                 event
  422.         );
  423.  
  424.         selectedId = null;
  425.  
  426.         if (selectedItem != null) {
  427.             Marker m = clusterRender.getMarker(selectedItem);
  428.  
  429.             if (m != null) {
  430.                 m.setIcon(HaveIcon.getIcon(mapView, selectedItem.getPrice(), selectedItem.getCountry(), HaveIcon.HaveIconState.VISITED));
  431.                 if (oldSelectedZIndex != Float.MIN_VALUE) {
  432.                     m.setZIndex(oldSelectedZIndex);
  433.                 }
  434.             }
  435.  
  436.             oldSelectedZIndex = Float.MIN_VALUE;
  437.         }
  438.     }
  439.  
  440.     @Override
  441.     public void onCameraIdle() {
  442.         WritableMap event = Arguments.createMap();
  443.         LatLngBounds bounds = map.getProjection().getVisibleRegion().latLngBounds;
  444.  
  445.         WritableMap ne = Arguments.createMap();
  446.  
  447.         ne.putDouble("latitude", bounds.northeast.latitude);
  448.         ne.putDouble("longitude", bounds.northeast.longitude);
  449.  
  450.         WritableMap sw = Arguments.createMap();
  451.  
  452.         sw.putDouble("latitude", bounds.southwest.latitude);
  453.         sw.putDouble("longitude", bounds.southwest.longitude);
  454.  
  455.         event.putMap("northeast", ne);
  456.         event.putMap("southwest", sw);
  457.  
  458.         ReactContext reactContext = (ReactContext) mapView.getContext();
  459.         reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
  460.                 mapView.getId(),
  461.                 "cameraIdle",
  462.                 event);
  463.  
  464.         if (clusterManager != null) {
  465.             clusterManager.onCameraIdle();
  466.         }
  467.     }
  468.  
  469.     @Override
  470.     public void onMapLoaded() {
  471.  
  472.         if (boundsToMove != null) {
  473.             map.animateCamera(CameraUpdateFactory.newLatLngBounds(boundsToMove, 0));
  474.             boundsToMove = null;
  475.         }
  476.  
  477.         clusterManager = new ClusterManager<ProfileClusterItem>(mapView.getContext(), map);
  478.  
  479.         IconGenerator gen = new IconGenerator(mapView.getContext());
  480.         gen.setBackground(mapView.getContext().getDrawable(R.drawable.map_cluster));
  481.  
  482.         LayoutInflater myInflater = (LayoutInflater) mapView.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  483.         View activityView = myInflater.inflate(R.layout.cluster_view, null, false);
  484.         gen.setContentView(activityView);
  485.         clusterRender = new DefaultClusterRenderer<ProfileClusterItem>(mapView.getContext(), map, clusterManager) {
  486.             @Override
  487.             protected void onBeforeClusterRendered(Cluster<ProfileClusterItem> cluster, MarkerOptions markerOptions) {
  488.                 int clusterNum = cluster.getSize();
  489.                 markerOptions.icon(BitmapDescriptorFactory.fromBitmap(gen.makeIcon(Integer.toString(clusterNum))));
  490.             }
  491.  
  492.             @Override
  493.             protected void onClusterUpdated(Cluster<ProfileClusterItem> cluster, Marker marker) {
  494.                 int clusterNum = cluster.getSize();
  495.                 marker.setIcon(BitmapDescriptorFactory.fromBitmap(gen.makeIcon(Integer.toString(clusterNum))));
  496.             }
  497.  
  498.             @Override
  499.             protected void onBeforeClusterItemRendered(ProfileClusterItem item, MarkerOptions markerOptions) {
  500.                 markerOptions.icon(item.getIcon());
  501.                 markerOptions.snippet(item.getSnippet());
  502.                 markerOptions.title(item.getTitle());
  503.             }
  504.         };
  505.  
  506.         clusterRender.setMinClusterSize(5);
  507.  
  508.         DisplayMetrics displayMetrics = mapView.getContext().getResources().getDisplayMetrics();
  509.  
  510.         clusterManager.setAlgorithm(new NonHierarchicalViewBasedAlgorithm<ProfileClusterItem>(displayMetrics.widthPixels, displayMetrics.heightPixels));
  511.         clusterManager.setRenderer(clusterRender);
  512.         clusterManager.setOnClusterItemClickListener(this);
  513.  
  514.         mapReady = true;
  515.  
  516.         if (renderPinsOnLoad) {
  517.             this.renderPins();
  518.         }
  519.     }
  520.  
  521.     @Override
  522.     public boolean onClusterItemClick(ClusterItem item) {
  523.         if (item == null) {
  524.             return true;
  525.         }
  526.  
  527.         map.animateCamera(CameraUpdateFactory.newLatLng(item.getPosition()), 300, null);
  528.         String profileId = (String) ((ProfileClusterItem) item).getProfileId();
  529.         WritableMap event = Arguments.createMap();
  530.         event.putString("profileId", profileId);
  531.  
  532.         if (selectedItem != null && selectedItem != item) {
  533.             Marker m = clusterRender.getMarker(selectedItem);
  534.  
  535.             if (m != null) {
  536.                 m.setIcon(HaveIcon.getIcon(mapView, ((ProfileClusterItem) selectedItem).getPrice(), ((ProfileClusterItem) selectedItem).getCountry(), HaveIcon.HaveIconState.VISITED));
  537.  
  538.                 if (oldSelectedZIndex != -1) {
  539.                     m.setZIndex(oldSelectedZIndex);
  540.                 }
  541.             }
  542.         }
  543.  
  544.         if (((ProfileClusterItem) item).getHaveOrNeed().equals("h")) {
  545.  
  546.  
  547.             Marker marker = clusterRender.getMarker((ProfileClusterItem) item);
  548.  
  549.             if (marker != null) {
  550.                 marker.setIcon(HaveIcon.getIcon(mapView, ((ProfileClusterItem) item).getPrice(), ((ProfileClusterItem) item).getCountry(), HaveIcon.HaveIconState.ACTIVE));
  551.  
  552.                 oldSelectedZIndex = marker.getZIndex();
  553.                 selectedItem = (ProfileClusterItem) item;
  554.                 marker.setZIndex(Float.MAX_VALUE);
  555.             }
  556.  
  557.             selectedId = profileId;
  558.  
  559.             visitedIds.add(selectedId);
  560.         } else {
  561.             selectedItem = null;
  562.             selectedId = null;
  563.         }
  564.  
  565.  
  566.         ReactContext reactContext = (ReactContext) mapView.getContext();
  567.         reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
  568.                 mapView.getId(),
  569.                 "pinClick",
  570.                 event);
  571.         return true;
  572.     }
  573. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement