ソースを参照

refactor(更新) :
- 地图显示问题处理

周文健 5 ヶ月 前
コミット
96b17571b9

+ 1 - 1
app/build.gradle

@@ -127,7 +127,7 @@ dependencies {
     implementation 'com.github.bumptech.glide:glide:4.11.0'
 
     // https://github.com/onlylemi/MapView
-    implementation 'com.github.onlylemi:mapview:v1.0'
+//    implementation 'com.github.onlylemi:mapview:v1.0'
 
     // CameraX 核心库
     implementation "androidx.camera:camera-core:1.2.0"

+ 1 - 0
app/src/main/java/com/grkj/iscs/ble/BleConnectionManager.kt

@@ -245,6 +245,7 @@ object BleConnectionManager {
                 // 没有扫描到
                 if (scanResultList?.none { it.mac == mac } == true) {
                     LogUtil.w("$mac is not scanned")
+                    BusinessManager.unregisterConnectListener(mac)
                     prepareDoneCallBack?.invoke(false, null)
                 }
             }

+ 3 - 0
app/src/main/java/com/grkj/iscs/modbus/ModBusController.kt

@@ -959,6 +959,9 @@ object ModBusController {
 
         val keyList = keyDockList
             .flatMap { it.deviceList }
+            .apply {
+                LogUtil.i("keyStatus:${this}")
+            }
             .filterIsInstance<DockBean.KeyBean>()
             .filterIndexed { idx, _ -> (idx + 1) !in slotCols }
             .filter { kb ->

+ 1 - 1
app/src/main/java/com/grkj/iscs/view/fragment/StepFragment.kt

@@ -219,9 +219,9 @@ class StepFragment(val goBack: () -> Unit, val changePage: (PageChangeBO) -> Uni
                 })
                 mBinding?.mapview?.addLayer(stationLayer)
                 stationLayer?.setRatio(mapRatio)
+                mBinding?.mapview?.setMinZoom(mBinding?.mapview?.currentZoom ?: 0f)
                 ThreadUtils.runOnMain {
                     mBinding?.mapview?.visibility = View.VISIBLE
-                    mBinding?.mapview?.currentRotateDegrees = 0f
                 }
             }
 

+ 1 - 1
app/src/main/java/com/grkj/iscs/view/fragment/SwitchStatusFragment.kt

@@ -163,9 +163,9 @@ class SwitchStatusFragment :
                 stationLayer?.setRatio(mapRatio)
                 stationLayer?.stopAnimation()
                 stationLayer?.startAnimation()
+                mBinding?.mapview?.refresh()
                 ThreadUtils.runOnMain {
                     mBinding?.mapview?.visibility = View.VISIBLE
-                    mBinding?.mapview?.currentRotateDegrees = 0f
                 }
             }
 

+ 319 - 0
app/src/main/java/com/onlylemi/mapview/library/MapView.java

@@ -0,0 +1,319 @@
+package com.onlylemi.mapview.library;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Picture;
+import android.graphics.PointF;
+import android.graphics.SurfaceTexture;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.TextureView;
+
+import com.grkj.iscs.util.log.LogUtil;
+import com.onlylemi.mapview.library.layer.MapBaseLayer;
+import com.onlylemi.mapview.library.layer.MapLayer;
+import com.onlylemi.mapview.library.utils.MapMath;
+import com.onlylemi.mapview.library.utils.MapUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Java版:TextureView替代SurfaceView的MapView,实现独立画布
+ * 修复:支持在任意缩放级别下既能放大也能缩小。
+ */
+public class MapView extends TextureView implements TextureView.SurfaceTextureListener {
+
+    private SurfaceTexture surface;
+    private boolean isMapLoadFinish = false;
+    private final List<MapBaseLayer> layers = new ArrayList<>();
+    private MapLayer mapLayer;
+
+    private final Matrix saveMatrix = new Matrix();
+    private final Matrix currentMatrix = new Matrix();
+    private float currentZoom = 1.0f;
+    private float saveZoom = 1.0f;           // 默认初始保存zoom为1
+    private float currentRotateDegrees = 0.0f;
+    private float saveRotateDegrees = 0.0f;
+
+    private float minZoom = 0.5f;
+    private float maxZoom = 3.0f;
+    private boolean isScaleAndRotateTogether = false;
+
+    private final PointF startTouch = new PointF();
+    private final PointF lastMove = new PointF();
+    private final PointF mid = new PointF();
+    private float oldDist = 0f;
+    private float oldDegree = 0f;
+    private int currentTouchState = TOUCH_STATE_NO;
+
+    private MapViewListener mapViewListener;
+
+    public static final int TOUCH_STATE_NO = 0;
+    public static final int TOUCH_STATE_SCROLL = 1;
+    public static final int TOUCH_STATE_SCALE = 2;
+    public static final int TOUCH_STATE_ROTATE = 3;
+    public static final int TOUCH_STATE_TWO_POINTED = 4;
+
+    public MapView(Context context) {
+        this(context, null);
+    }
+
+    public MapView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public MapView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init();
+    }
+
+    private void init() {
+        setSurfaceTextureListener(this);
+        setOpaque(true);
+        setClickable(true);
+    }
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture st, int width, int height) {
+        surface = st;
+        refresh();
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture st, int width, int height) {
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture st) {
+        surface = null;
+        return true;
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture st) {
+    }
+
+    public void loadMap(Bitmap bitmap) {
+        loadMap(MapUtils.getPictureFromBitmap(bitmap));
+    }
+
+    public void loadMap(final Picture picture) {
+        isMapLoadFinish = false;
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                if (picture != null) {
+                    if (mapLayer == null) {
+                        mapLayer = new MapLayer(MapView.this);
+                        layers.add(mapLayer);
+                    }
+                    mapLayer.setImage(picture);
+                    if (mapViewListener != null) {
+                        mapViewListener.onMapLoadSuccess();
+                    }
+                    isMapLoadFinish = true;
+                    post(new Runnable() {
+                        @Override
+                        public void run() {
+                            refresh();
+                        }
+                    });
+                } else {
+                    if (mapViewListener != null) {
+                        mapViewListener.onMapLoadFail();
+                    }
+                }
+            }
+        }).start();
+    }
+
+    public void refresh() {
+        if (surface == null || !isMapLoadFinish) return;
+        Canvas canvas = lockCanvas();
+        if (canvas != null) {
+            canvas.drawColor(Color.WHITE);
+            for (MapBaseLayer layer : layers) {
+                if (layer.isVisible) {
+                    layer.draw(canvas, currentMatrix, currentZoom, currentRotateDegrees);
+                }
+            }
+            unlockCanvasAndPost(canvas);
+        }
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (!isMapLoadFinish) return false;
+        int action = event.getAction() & MotionEvent.ACTION_MASK;
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                saveMatrix.set(currentMatrix);
+                startTouch.set(event.getX(), event.getY());
+                lastMove.set(event.getX(), event.getY());
+                currentTouchState = TOUCH_STATE_SCROLL;
+                break;
+            case MotionEvent.ACTION_POINTER_DOWN:
+                if (event.getPointerCount() == 2) {
+                    saveMatrix.set(currentMatrix);
+                    saveZoom = currentZoom;
+                    saveRotateDegrees = currentRotateDegrees;
+                    mid.set(midPoint(event));
+                    oldDist = distance(event, mid);
+                    oldDegree = rotation(event, mid);
+                    currentTouchState = TOUCH_STATE_TWO_POINTED;
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+                if (withFloorPlan(event.getX(), event.getY())) {
+                    for (MapBaseLayer layer : layers) {
+                        layer.onTouch(event);
+                    }
+                }
+                currentTouchState = TOUCH_STATE_NO;
+                break;
+            case MotionEvent.ACTION_MOVE:
+                if (currentTouchState == TOUCH_STATE_SCROLL) {
+                    currentMatrix.set(saveMatrix);
+                    currentMatrix.postTranslate(event.getX() - startTouch.x,
+                            event.getY() - startTouch.y);
+                } else if (currentTouchState == TOUCH_STATE_TWO_POINTED) {
+                    // 始终按缩放处理,避免旋转误判让缩小失效
+                    float newDist = distance(event, mid);
+                    float scale = newDist / oldDist;
+                    if (scale * saveZoom < minZoom) {
+                        scale = minZoom / saveZoom;
+                    } else if (scale * saveZoom > maxZoom) {
+                        scale = maxZoom / saveZoom;
+                    }
+                    currentZoom = scale * saveZoom;
+                    LogUtil.INSTANCE.i("当前比例:" + currentZoom);
+                    currentMatrix.set(saveMatrix);
+                    currentMatrix.postScale(scale, scale, mid.x, mid.y);
+                }
+                lastMove.set(event.getX(), event.getY());
+                refresh();
+                break;
+        }
+        return true;
+    }
+
+    public void setMapViewListener(MapViewListener listener) {
+        this.mapViewListener = listener;
+    }
+
+    public float[] convertMapXYToScreenXY(float x, float y) {
+        Matrix inv = new Matrix();
+        currentMatrix.invert(inv);
+        float[] pts = {x, y};
+        inv.mapPoints(pts);
+        return pts;
+    }
+
+    public boolean isMapLoadFinish() {
+        return isMapLoadFinish;
+    }
+
+    public void addLayer(MapBaseLayer layer) {
+        layers.add(layer);
+    }
+
+    public List<MapBaseLayer> getLayers() {
+        return layers;
+    }
+
+    public void translate(float x, float y) {
+        currentMatrix.postTranslate(x, y);
+        refresh();
+    }
+
+    public void mapCenterWithPoint(float x, float y) {
+        float[] pts = {x, y};
+        currentMatrix.mapPoints(pts);
+        float dx = getWidth() / 2f - pts[0];
+        float dy = getHeight() / 2f - pts[1];
+        currentMatrix.postTranslate(dx, dy);
+        refresh();
+    }
+
+    public float getCurrentRotateDegrees() {
+        return currentRotateDegrees;
+    }
+
+    public void setCurrentRotateDegrees(float degrees) {
+        mapCenterWithPoint(
+                mapLayer != null ? mapLayer.getImage().getWidth() / 2f : 0f,
+                mapLayer != null ? mapLayer.getImage().getHeight() / 2f : 0f
+        );
+        setCurrentRotateDegrees(degrees, getWidth() / 2f, getHeight() / 2f);
+    }
+
+    public void setCurrentRotateDegrees(float degrees, float px, float py) {
+        currentMatrix.postRotate(degrees - currentRotateDegrees, px, py);
+        currentRotateDegrees = (degrees % 360 + 360) % 360;
+        refresh();
+    }
+
+    public float getCurrentZoom() {
+        return currentZoom;
+    }
+
+    public void setCurrentZoom(float zoom) {
+        setCurrentZoom(zoom, getWidth() / 2f, getHeight() / 2f);
+    }
+
+    public void setCurrentZoom(float zoom, float px, float py) {
+        currentMatrix.postScale(zoom / currentZoom, zoom / currentZoom, px, py);
+        currentZoom = zoom;
+        refresh();
+    }
+
+    public boolean isScaleAndRotateTogether() {
+        return isScaleAndRotateTogether;
+    }
+
+    public void setScaleAndRotateTogether(boolean flag) {
+        this.isScaleAndRotateTogether = flag;
+    }
+
+    public void setMaxZoom(float max) {
+        this.maxZoom = max;
+    }
+
+    public void setMinZoom(float min) {
+        LogUtil.INSTANCE.i("最小缩放比:" + min);
+        this.minZoom = min;
+    }
+
+    private PointF midPoint(MotionEvent event) {
+        return MapMath.getMidPointBetweenTwoPoints(
+                event.getX(0), event.getY(0), event.getX(1), event.getY(1));
+    }
+
+    private float distance(MotionEvent event, PointF mid) {
+        return MapMath.getDistanceBetweenTwoPoints(
+                event.getX(0), event.getY(0), mid.x, mid.y);
+    }
+
+    private float rotation(MotionEvent event, PointF mid) {
+        return MapMath.getDegreeBetweenTwoPoints(
+                event.getX(0), event.getY(0), mid.x, mid.y);
+    }
+
+    public boolean withFloorPlan(float x, float y) {
+        float[] pts = convertMapXYToScreenXY(x, y);
+        Picture img = mapLayer != null ? mapLayer.getImage() : null;
+        return img != null && pts[0] > 0 && pts[0] < img.getWidth() && pts[1] > 0 && pts[1] < img.getHeight();
+    }
+
+    public float getMapWidth() {
+        return mapLayer != null ? mapLayer.getImage().getWidth() : 0f;
+    }
+
+    public float getMapHeight() {
+        return mapLayer != null ? mapLayer.getImage().getHeight() : 0f;
+    }
+}

+ 19 - 0
app/src/main/java/com/onlylemi/mapview/library/MapViewListener.java

@@ -0,0 +1,19 @@
+package com.onlylemi.mapview.library;
+
+/**
+ * MapViewListener
+ *
+ * @author: onlylemi
+ */
+public interface MapViewListener {
+
+    /**
+     * when mapview load complete to callback
+     */
+    void onMapLoadSuccess();
+
+    /**
+     * when mapview load error to callback
+     */
+    void onMapLoadFail();
+}

+ 59 - 0
app/src/main/java/com/onlylemi/mapview/library/layer/MapBaseLayer.java

@@ -0,0 +1,59 @@
+package com.onlylemi.mapview.library.layer;
+
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.util.TypedValue;
+import android.view.MotionEvent;
+
+import com.onlylemi.mapview.library.MapView;
+
+/**
+ * MapBaseLayer
+ *
+ * @author: onlylemi
+ */
+public abstract class MapBaseLayer {
+
+    // map layer level
+    protected static final int MAP_LEVEL = 0;
+    // location layer level
+    protected static final int LOCATION_LEVEL = Integer.MAX_VALUE;
+
+    // layer show level
+    public int level;
+    // layer is/not show
+    public boolean isVisible = true;
+
+    protected MapView mapView;
+
+    public MapBaseLayer(MapView mapView) {
+        this.mapView = mapView;
+    }
+
+    /**
+     * touch event
+     *
+     * @param event
+     */
+    public abstract void onTouch(MotionEvent event);
+
+    /**
+     * draw event
+     *
+     * @param canvas
+     * @param currentMatrix
+     * @param currentZoom
+     * @param currentRotateDegrees
+     */
+    public abstract void draw(Canvas canvas, Matrix currentMatrix, float currentZoom,
+                              float currentRotateDegrees);
+
+    public void setLevel(int level) {
+        this.level = level;
+    }
+
+    protected float setValue(float value) {
+        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, mapView.getResources()
+                .getDisplayMetrics());
+    }
+}

+ 109 - 0
app/src/main/java/com/onlylemi/mapview/library/layer/MapLayer.java

@@ -0,0 +1,109 @@
+package com.onlylemi.mapview.library.layer;
+
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Picture;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ViewTreeObserver;
+
+import com.onlylemi.mapview.library.MapView;
+
+/**
+ * MapLayer
+ *
+ * @author: onlylemi
+ */
+public class MapLayer extends MapBaseLayer {
+
+    private static final String TAG = "MapLayer";
+
+    private Picture image;
+    private boolean hasMeasured;
+
+    public MapLayer(MapView mapView) {
+        super(mapView);
+        level = MAP_LEVEL;
+    }
+
+    public void setImage(Picture image) {
+        this.image = image;
+
+        if (mapView.getWidth() == 0) {
+            ViewTreeObserver vto = mapView.getViewTreeObserver();
+            vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+                public boolean onPreDraw() {
+                    if (!hasMeasured) {
+                        initMapLayer();
+                        hasMeasured = true;
+                    }
+                    return true;
+                }
+            });
+        } else {
+            initMapLayer();
+        }
+    }
+
+
+    /**
+     * init map image layer
+     */
+    private void initMapLayer() {
+        float zoom = getInitZoom(mapView.getWidth(), mapView.getHeight(), image.getWidth(), image
+                .getHeight());
+        Log.i(TAG, Float.toString(zoom));
+        mapView.setMinZoom(zoom);
+        mapView.setCurrentZoom(zoom, 0, 0);
+
+        float width = mapView.getWidth() - zoom * image.getWidth();
+        float height = mapView.getHeight() - zoom * image.getHeight();
+
+        mapView.translate(width / 2, height / 2);
+    }
+
+    /**
+     * calculate init zoom
+     *
+     * @param viewWidth
+     * @param viewHeight
+     * @param imageWidth
+     * @param imageHeight
+     * @return
+     */
+    private float getInitZoom(float viewWidth, float viewHeight, float imageWidth,
+                              float imageHeight) {
+        float widthRatio = viewWidth / imageWidth;
+        float heightRatio = viewHeight / imageHeight;
+
+        Log.i(TAG, "widthRatio:" + widthRatio);
+        Log.i(TAG, "widthRatio:" + heightRatio);
+
+        if (widthRatio * imageHeight <= viewHeight) {
+            return widthRatio;
+        } else if (heightRatio * imageWidth <= viewWidth) {
+            return heightRatio;
+        }
+        return 0;
+    }
+
+    @Override
+    public void onTouch(MotionEvent event) {
+
+    }
+
+    @Override
+    public void draw(Canvas canvas, Matrix currentMatrix, float currentZoom, float
+            currentRotateDegrees) {
+        canvas.save();
+        canvas.setMatrix(currentMatrix);
+        if (image != null) {
+            canvas.drawPicture(image);
+        }
+        canvas.restore();
+    }
+
+    public Picture getImage() {
+        return image;
+    }
+}

+ 305 - 0
app/src/main/java/com/onlylemi/mapview/library/utils/MapMath.java

@@ -0,0 +1,305 @@
+package com.onlylemi.mapview.library.utils;
+
+import android.graphics.PointF;
+import android.util.Log;
+
+import com.onlylemi.mapview.library.utils.math.FloydAlgorithm;
+import com.onlylemi.mapview.library.utils.math.GeneticAlgorithm;
+import com.onlylemi.mapview.library.utils.math.TSPNearestNeighbour;
+
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * MapMath
+ *
+ * @author onlylemi
+ */
+public final class MapMath {
+
+    private MapMath() {}
+
+    /**
+     * the distance between two points
+     *
+     * @param x1
+     * @param y1
+     * @param x2
+     * @param y2
+     * @return
+     */
+    public static float getDistanceBetweenTwoPoints(float x1, float y1,
+                                                    float x2, float y2) {
+        return (float) Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
+    }
+
+    /**
+     * the distance between two points
+     *
+     * @param start
+     * @param end
+     * @return
+     */
+    public static float getDistanceBetweenTwoPoints(PointF start, PointF end) {
+        return (float) Math.sqrt(Math.pow(end.x - start.x, 2)
+                + Math.pow(end.y - start.y, 2));
+    }
+
+
+    /**
+     * the shortest path between two points (FloydAlgorithm)
+     *
+     * @param begin
+     * @param end
+     * @param matrix adjacency matrix
+     * @return
+     */
+    public static List<Integer> getShortestPathBetweenTwoPoints(int begin,
+                                                                int end, float[][] matrix) {
+        return FloydAlgorithm.getInstance().findCheapestPath(begin, end, matrix);
+    }
+
+    /**
+     * the best path between some points (NearestNeighbour tsp)
+     *
+     * @param matrix adjacency matrix
+     * @return
+     */
+    public static List<Integer> getBestPathBetweenPointsByNearestNeighbour(float[][] matrix) {
+        return TSPNearestNeighbour.getInstance().tsp(matrix);
+    }
+
+    /**
+     * the best path between some points (GeneticAlgorithm tsp)
+     *
+     * @param matrix
+     * @return
+     */
+    public static List<Integer> getBestPathBetweenPointsByGeneticAlgorithm(float[][] matrix) {
+        GeneticAlgorithm ga = GeneticAlgorithm.getInstance();
+        ga.setAutoNextGeneration(true);
+        ga.setMaxGeneration(200);
+        int[] best = ga.tsp(matrix);
+
+        List<Integer> result = new ArrayList<>(best.length);
+        for (int i = 0; i < best.length; i++) {
+            result.add(best[i]);
+        }
+        return result;
+    }
+
+
+    /**
+     * get the angle between two points and the horizontal plane
+     *
+     * @param start
+     * @param end
+     * @return
+     */
+    public static float getDegreeBetweenTwoPointsWithHorizontal(PointF start, PointF end) {
+        float angle = 90.0f;
+        if (start.x != end.x) {
+            angle = (float) Math.toDegrees(Math.atan((end.y - start.y)
+                    / (end.x - start.x)));
+            if (end.x < start.x && end.y >= start.y) {
+                angle = angle + 180.0f;
+            } else if (end.x < start.x && end.y > start.y) {
+                angle = angle - 180.f;
+            }
+        } else {
+            if (start.y < end.y) {
+                angle = 90.0f;
+            } else if (start.y > end.y) {
+                angle = -90.0f;
+            }
+        }
+        return angle;
+    }
+
+    /**
+     * get the angle between two points and the vertical plane
+     *
+     * @param start
+     * @param end
+     * @return
+     */
+    public static float getDegreeBetweenTwoPointsWithVertical(PointF start, PointF end) {
+        float angle = 90.0f;
+        if (start.y != end.y) {
+            angle = -(float) Math.toDegrees(Math.atan((end.x - start.x)
+                    / (end.y - start.y)));
+            if (end.y > start.y && end.x >= start.x) {
+                angle = angle + 180.0f;
+            } else if (end.y > start.y && end.x > start.x) {
+                angle = angle - 180.f;
+            }
+        } else {
+            if (start.x < end.x) {
+                angle = 90.0f;
+            } else if (start.x > end.x) {
+                angle = -90.0f;
+            }
+        }
+        return angle;
+    }
+
+    /**
+     * get degree between two points
+     *
+     * @param x1
+     * @param y1
+     * @param x2
+     * @param y2
+     * @return
+     */
+    public static float getDegreeBetweenTwoPoints(float x1, float y1, float x2, float y2) {
+        double radians = Math.atan2(y1 - y2, x1 - x2);
+        return (float) Math.toDegrees(radians);
+    }
+
+    /**
+     * get degree between two points
+     *
+     * @param start
+     * @param end
+     * @return
+     */
+    public static float getDegreeBetweenTwoPoints(PointF start, PointF end) {
+        return getDegreeBetweenTwoPoints(start.x, start.y, end.x, end.y);
+    }
+
+    /**
+     * The coordinates of the midpoint between two points are obtained
+     *
+     * @param x1
+     * @param y1
+     * @param x2
+     * @param y2
+     * @return
+     */
+    public static PointF getMidPointBetweenTwoPoints(float x1, float y1, float x2, float y2) {
+        return new PointF((x1 + x2) / 2, (y1 + y2) / 2);
+    }
+
+    /**
+     * The coordinates of the midpoint between two points are obtained
+     *
+     * @param start
+     * @param end
+     * @return
+     */
+    public static PointF getMidPointBetweenTwoPoints(PointF start, PointF end) {
+        return getMidPointBetweenTwoPoints(start.x, start.y, end.x, end.y);
+    }
+
+    /**
+     * Get the coordinates of any point between two points
+     *
+     * @param start
+     * @param end
+     * @param value
+     * @return
+     */
+    public static PointF getEveryPointBetweenTwoPoints(PointF start, PointF end, float value) {
+        // y=kx+b
+        float x, y;
+        // with slope
+        if (start.x != end.x) {
+            float k = (end.y - start.y) / (end.x - start.x);
+            float b = end.y - k * end.x;
+
+            if (end.x > start.x) {
+                x = Math.min(end.x, start.x) + (end.x - start.x) * value;
+            } else {
+                x = Math.max(end.x, start.x) + (end.x - start.x) * value;
+            }
+            y = k * x + b;
+        } else { // no slope
+            x = start.x;
+            if (end.y > start.y) {
+                y = Math.min(end.y, start.y) + (end.y - start.y) * value;
+            } else {
+                y = Math.max(end.y, start.y) + (end.y - start.y) * value;
+            }
+        }
+        return new PointF(x, y);
+    }
+
+
+    /**
+     * Get a shortest distance from point to line
+     *
+     * @param point
+     * @param linePoint1 Determine the first point of a straight line
+     * @param linePoint2 Determine the second point of a straight line
+     * @return
+     */
+    public static float getDistanceFromPointToLine(PointF point, PointF linePoint1, PointF
+            linePoint2) {
+        // y = kx + b;
+        // d = |kx-y+b| / √(k^2+1)
+        float d;
+        if (linePoint1.x != linePoint2.x) { // with slope
+            float k = (linePoint2.y - linePoint1.y) / (linePoint2.x - linePoint1.x);
+            float b = linePoint2.y - k * linePoint2.x;
+            d = Math.abs(k * point.x - point.y + b) / (float) Math.sqrt(k * k + 1);
+        } else { // no slope
+            d = Math.abs(point.x - linePoint1.x);
+        }
+        return d;
+    }
+
+    /**
+     * get intersection coordinates from a point to a line
+     *
+     * @param point
+     * @param linePoint1
+     * @param linePoint2
+     * @return
+     */
+    public static PointF getIntersectionCoordinatesFromPointToLine(PointF point, PointF linePoint1, PointF
+            linePoint2) {
+        // y = kx + b;
+        float x, y;
+        if (linePoint1.x != linePoint2.x) { // with slope
+            float k = (linePoint2.y - linePoint1.y) / (linePoint2.x - linePoint1.x);
+            float b = linePoint2.y - k * linePoint2.x;
+            // The equation of point
+            if (k != 0) {
+                float kV = -1 / k;
+                float bV = point.y - kV * point.x;
+                x = (b - bV) / (kV - k);
+                y = kV * x + bV;
+            } else {
+                x = point.x;
+                y = linePoint1.y;
+            }
+        } else { // no slope
+            x = linePoint1.x;
+            y = point.y;
+        }
+        return new PointF(x, y);
+    }
+
+    /**
+     * is/not obtuse angle between a point and a line
+     *
+     * @param point
+     * @param linePoint1
+     * @param linePoint2
+     * @return
+     */
+    public static boolean isObtuseAnglePointAndLine(PointF point, PointF linePoint1, PointF
+            linePoint2) {
+        // A*A + B*B < C*C
+        float p_l1, p_l2, l1_l2;
+        p_l1 = getDistanceBetweenTwoPoints(point, linePoint1);
+        p_l2 = getDistanceBetweenTwoPoints(point, linePoint2);
+        l1_l2 = getDistanceBetweenTwoPoints(linePoint1, linePoint2);
+
+        return ((p_l1 * p_l1 + l1_l2 * l1_l2) < p_l2 * p_l2)
+                || ((p_l2 * p_l2 + l1_l2 * l1_l2) < p_l1 * p_l1);
+    }
+
+}

+ 334 - 0
app/src/main/java/com/onlylemi/mapview/library/utils/MapUtils.java

@@ -0,0 +1,334 @@
+package com.onlylemi.mapview.library.utils;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Picture;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * MapUtils
+ *
+ * @author onlylemi
+ */
+public final class MapUtils {
+
+    private static final String TAG = "MapUtils: ";
+
+    private static final float INF = Float.MAX_VALUE;
+    private static int nodesSize;
+    private static int nodesContactSize;
+
+    private MapUtils() {}
+
+    public static void init(int nodessize, int nodescontactsize) {
+        nodesSize = nodessize;
+        nodesContactSize = nodescontactsize;
+    }
+
+    /**
+     * Get the distance between points
+     *
+     * @param nodes
+     * @param list
+     * @return
+     */
+    public static float getDistanceBetweenList(List<PointF> nodes,
+                                               List<Integer> list) {
+        float distance = 0;
+        for (int i = 0; i < list.size() - 1; i++) {
+            distance += MapMath.getDistanceBetweenTwoPoints(nodes.get(list.get(i)),
+                    nodes.get(list.get(i + 1)));
+        }
+        return distance;
+    }
+
+    /**
+     * get degrees between two points(list) with horizontal plane
+     *
+     * @param routeList
+     * @param nodes
+     * @return
+     */
+    public static List<Float> getDegreeBetweenTwoPointsWithHorizontal(List<Integer> routeList,
+                                                                      List<PointF> nodes) {
+        List<Float> routeListDegrees = new ArrayList<>();
+        for (int i = 0; i < routeList.size() - 1; i++) {
+            routeListDegrees.add(MapMath.getDegreeBetweenTwoPointsWithHorizontal(nodes.get
+                            (routeList.get(i)),
+                    nodes.get(routeList.get(i + 1))));
+        }
+        return routeListDegrees;
+    }
+
+    /**
+     * get degrees between two points(list) with vertical plane
+     *
+     * @param routeList
+     * @param nodes
+     * @return
+     */
+    public static List<Float> getDegreeBetweenTwoPointsWithVertical(List<Integer> routeList,
+                                                                    List<PointF> nodes) {
+        List<Float> routeListDegrees = new ArrayList<>();
+        for (int i = 0; i < routeList.size() - 1; i++) {
+            routeListDegrees.add(MapMath.getDegreeBetweenTwoPointsWithVertical(nodes.get
+                            (routeList.get(i)),
+                    nodes.get(routeList.get(i + 1))));
+        }
+        return routeListDegrees;
+    }
+
+    /**
+     * get shortest path between two points
+     *
+     * @param start        start point
+     * @param end          end point
+     * @param nodes        nodes list
+     * @param nodesContact nodesContact list
+     * @return
+     */
+    public static List<Integer> getShortestPathBetweenTwoPoints(int start,
+                                                                int end, List<PointF> nodes,
+                                                                List<PointF> nodesContact) {
+        float[][] matrix = getMatrixBetweenFloorPlanNodes(nodes, nodesContact);
+
+        return MapMath.getShortestPathBetweenTwoPoints(start, end, matrix);
+    }
+
+    /**
+     * get best path between points
+     *
+     * @param points
+     * @param nodes
+     * @param nodesContact
+     * @return
+     */
+    public static List<Integer> getBestPathBetweenPoints(int[] points, List<PointF> nodes,
+                                                         List<PointF> nodesContact) {
+        // adjacency matrix
+        float[][] matrix = new float[points.length][points.length];
+        for (int i = 0; i < matrix.length; i++) {
+            for (int j = i; j < matrix[i].length; j++) {
+                if (i == j) {
+                    matrix[i][j] = INF;
+                } else {
+                    matrix[i][j] = getDistanceBetweenList(
+                            nodes, getShortestPathBetweenTwoPoints(points[i],
+                                    points[j], nodes, nodesContact));
+                    matrix[j][i] = matrix[i][j];
+                }
+            }
+        }
+
+        // TSP to get best path
+        List<Integer> routeList = new ArrayList<>();
+        List<Integer> result = MapMath.getBestPathBetweenPointsByGeneticAlgorithm(matrix);
+        for (int i = 0; i < result.size() - 1; i++) {
+            int size = routeList.size();
+            routeList.addAll(getShortestPathBetweenTwoPoints(
+                    points[result.get(i)], points[result.get(i + 1)], nodes,
+                    nodesContact));
+            if (i != 0) {
+                routeList.remove(size);
+            }
+        }
+        return routeList;
+    }
+
+
+    /**
+     * get best path between points
+     *
+     * @param pointList
+     * @param nodes
+     * @param nodesContact
+     * @return
+     */
+    public static List<Integer> getBestPathBetweenPoints(List<PointF> pointList,
+                                                         List<PointF> nodes, List<PointF>
+                                                                 nodesContact) {
+        if (nodesSize != nodes.size()) {
+            int value = nodes.size() - nodesSize;
+            for (int i = 0; i < value; i++) {
+                nodes.remove(nodes.size() - 1);
+            }
+            value = nodesContact.size() - nodesContactSize;
+            for (int i = 0; i < value; i++) {
+                nodesContact.remove(nodesContact.size() - 1);
+            }
+        }
+
+        //find the point on the nearest route
+        int[] points = new int[pointList.size()];
+        for (int i = 0; i < pointList.size(); i++) {
+            addPointToList(pointList.get(i), nodes, nodesContact);
+            points[i] = nodes.size() - 1;
+        }
+
+        return getBestPathBetweenPoints(points, nodes, nodesContact);
+    }
+
+    /**
+     * get shortest distance between two points
+     *
+     * @param start
+     * @param end
+     * @param nodes
+     * @param nodesContact
+     * @return
+     */
+    public static float getShortestDistanceBetweenTwoPoints(int start, int end,
+                                                            List<PointF> nodes, List<PointF>
+                                                                    nodesContact) {
+        List<Integer> list = getShortestPathBetweenTwoPoints(start, end, nodes,
+                nodesContact);
+        return getDistanceBetweenList(nodes, list);
+    }
+
+    /**
+     * adjacency matrix with points
+     *
+     * @param nodes
+     * @param nodesContact
+     * @return
+     */
+    public static float[][] getMatrixBetweenFloorPlanNodes(List<PointF> nodes, List<PointF>
+            nodesContact) {
+        // set default is INF
+        float[][] matrix = new float[nodes.size()][nodes.size()];
+        for (int i = 0; i < matrix.length; i++) {
+            for (int j = 0; j < matrix[i].length; j++) {
+                matrix[i][j] = INF;
+            }
+        }
+
+        // set value for matrix
+        for (int i = 0; i < nodesContact.size(); i++) {
+            matrix[(int) nodesContact.get(i).x][(int) nodesContact.get(i).y] = MapMath
+                    .getDistanceBetweenTwoPoints(nodes.get((int) nodesContact.get(i).x),
+                            nodes.get((int) nodesContact.get(i).y));
+
+            matrix[(int) nodesContact.get(i).y][(int) nodesContact.get(i).x] = matrix[(int)
+                    nodesContact
+                            .get(i).x][(int) nodesContact.get(i).y];
+        }
+
+        return matrix;
+    }
+
+    /**
+     * get shortest distance between two points
+     *
+     * @param start
+     * @param end
+     * @param nodes
+     * @param nodesContact
+     * @return
+     */
+    public static List<Integer> getShortestDistanceBetweenTwoPoints(PointF start, PointF end,
+                                                                    List<PointF> nodes,
+                                                                    List<PointF> nodesContact) {
+        if (nodesSize != nodes.size()) {
+            int value = nodes.size() - nodesSize;
+            for (int i = 0; i < value; i++) {
+                nodes.remove(nodes.size() - 1);
+            }
+            value = nodesContact.size() - nodesContactSize;
+            for (int i = 0; i < value; i++) {
+                nodesContact.remove(nodesContact.size() - 1);
+            }
+        }
+
+        addPointToList(start, nodes, nodesContact);
+        addPointToList(end, nodes, nodesContact);
+
+        return getShortestPathBetweenTwoPoints(nodes.size() - 2, nodes.size() - 1, nodes,
+                nodesContact);
+    }
+
+    /**
+     * get the shortest path from the position point to the target point in the map
+     *
+     * @param position
+     * @param target
+     * @param nodes
+     * @param nodesContact
+     * @return
+     */
+    public static List<Integer> getShortestDistanceBetweenTwoPoints(PointF position, int target,
+                                                                    List<PointF> nodes,
+                                                                    List<PointF> nodesContact) {
+        if (nodesSize != nodes.size()) {
+            int value = nodes.size() - nodesSize;
+            for (int i = 0; i < value; i++) {
+                nodes.remove(nodes.size() - 1);
+            }
+            value = nodesContact.size() - nodesContactSize;
+            for (int i = 0; i < value; i++) {
+                nodesContact.remove(nodesContact.size() - 1);
+            }
+        }
+
+        addPointToList(position, nodes, nodesContact);
+
+        return getShortestPathBetweenTwoPoints(nodes.size() - 1, target, nodes, nodesContact);
+    }
+
+    /**
+     * add point to list
+     *
+     * @param point
+     * @param nodes
+     * @param nodesContact
+     */
+    private static void addPointToList(PointF point, List<PointF> nodes, List<PointF>
+            nodesContact) {
+        if (point != null) {
+            PointF pV = null;
+            int po1 = 0, po2 = 0;
+            float min1 = INF;
+            for (int i = 0; i < nodesContact.size() - 1; i++) {
+                PointF p1 = nodes.get((int) nodesContact.get(i).x);
+                PointF p2 = nodes.get((int) nodesContact.get(i).y);
+                if (!MapMath.isObtuseAnglePointAndLine(point, p1, p2)) {
+                    float minDis = MapMath.getDistanceFromPointToLine(point, p1, p2);
+                    if (min1 > minDis) {
+                        pV = MapMath.getIntersectionCoordinatesFromPointToLine(point, p1, p2);
+                        min1 = minDis;
+                        po1 = (int) nodesContact.get(i).x;
+                        po2 = (int) nodesContact.get(i).y;
+                    }
+                }
+            }
+            // get intersection
+            nodes.add(pV);
+            //Log.i(TAG, "node=" + (nodes.size() - 1) + ", po1=" + po1 + ", po2=" + po2);
+            nodesContact.add(new PointF(po1, nodes.size() - 1));
+            nodesContact.add(new PointF(po2, nodes.size() - 1));
+        }
+    }
+
+    /**
+     * bitmap to picture
+     *
+     * @param bitmap
+     * @return
+     */
+    public static Picture getPictureFromBitmap(Bitmap bitmap) {
+        Picture picture = new Picture();
+        Canvas canvas = picture.beginRecording(bitmap.getWidth(),
+                bitmap.getHeight());
+        canvas.drawBitmap(
+                bitmap,
+                null,
+                new RectF(0f, 0f, (float) bitmap.getWidth(), (float) bitmap
+                        .getHeight()), null);
+        picture.endRecording();
+        return picture;
+    }
+}

+ 87 - 0
app/src/main/java/com/onlylemi/mapview/library/utils/math/FloydAlgorithm.java

@@ -0,0 +1,87 @@
+package com.onlylemi.mapview.library.utils.math;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * FloydAlgorithm
+ *
+ * @author: onlylemi
+ */
+public final class FloydAlgorithm {
+
+    private static final int INF = Integer.MAX_VALUE;
+    private float[][] dist;
+
+    // the shortest path from i to j
+    private int[][] path;
+    private List<Integer> result;
+
+    public static FloydAlgorithm getInstance() {
+        return FloydAlgorithmHolder.instance;
+    }
+
+    private static class FloydAlgorithmHolder {
+        private static FloydAlgorithm instance = new FloydAlgorithm();
+    }
+
+    private void init(float[][] matrix) {
+        dist = null;
+        path = null;
+        result = new ArrayList<>();
+
+        this.dist = new float[matrix.length][matrix.length];
+        this.path = new int[matrix.length][matrix.length];
+    }
+
+    /**
+     * the shortest between begin to end
+     *
+     * @param begin
+     * @param end
+     * @param matrix
+     */
+    public List<Integer> findCheapestPath(int begin, int end, float[][] matrix) {
+        init(matrix);
+
+        floyd(matrix);
+        result.add(begin);
+        findPath(begin, end);
+        result.add(end);
+
+        return result;
+    }
+
+    private void findPath(int i, int j) {
+        int k = path[i][j];
+        if (k == -1)
+            return;
+        findPath(i, k); // recursion
+        result.add(k);
+        findPath(k, j);
+    }
+
+    private void floyd(float[][] matrix) {
+        int size = matrix.length;
+        // initialize dist and path
+        for (int i = 0; i < size; i++) {
+            for (int j = 0; j < size; j++) {
+                path[i][j] = -1;
+                dist[i][j] = matrix[i][j];
+            }
+        }
+        for (int k = 0; k < size; k++) {
+            for (int i = 0; i < size; i++) {
+                for (int j = 0; j < size; j++) {
+                    if (dist[i][k] != INF && dist[k][j] != INF
+                            && dist[i][k] + dist[k][j] < dist[i][j]) {
+                        dist[i][j] = dist[i][k] + dist[k][j];
+                        path[i][j] = k;
+                    }
+                }
+            }
+        }
+
+    }
+
+}

+ 440 - 0
app/src/main/java/com/onlylemi/mapview/library/utils/math/GeneticAlgorithm.java

@@ -0,0 +1,440 @@
+package com.onlylemi.mapview.library.utils.math;
+
+import java.util.Arrays;
+import java.util.Random;
+
+/**
+ * GeneticAlgorithm
+ *
+ * @author: onlylemi
+ */
+public class GeneticAlgorithm {
+
+    private static final float DEFAULT_CROSSOVER_PROBABILITY = 0.9f; // 默认交叉概率
+    private static final float DEFAULT_MUTATION_PROBABILITY = 0.01f; // 默认突变概率
+    private static final int DEFAULT_POPULATION_SIZE = 30; // 默认种群数量
+    private static final int PREVIOUS = 0;
+    private static final int NEXT = 1;
+
+    private float crossoverProbability = DEFAULT_CROSSOVER_PROBABILITY; // 交叉概率
+    private float mutationProbability = DEFAULT_MUTATION_PROBABILITY; // 突变概率
+
+    private int populationSize = DEFAULT_POPULATION_SIZE; // 种群数量
+    private int mutationTimes = 0; // 变异次数
+    private int currentGeneration = 0; // 当前的一代
+
+    private int maxGeneration = 1000; // 最大代数
+    private int pointNum;
+    private int[][] population; // 种群集
+
+    private float[][] dist; // 点集间的邻接矩阵
+    private int[] bestIndivial; // 最短的结果集
+    private float bestDist; // 最短的距离
+    private int currentBestPosition; // 当前最好个体的位置
+
+    private float currentBestDist; // 当前最好个体的距离
+    private float[] values; // 种群中每个个体的dist
+    private float[] fitnessValues; // 适应度集
+
+    private float[] roulette;
+
+    private boolean isAutoNextGeneration = false;
+
+    private static Random rd;
+
+    public static GeneticAlgorithm getInstance() {
+        return GeneticAlgorithmHolder.instance;
+    }
+
+    private static class GeneticAlgorithmHolder {
+        private static GeneticAlgorithm instance = new GeneticAlgorithm();
+    }
+
+    /**
+     * 点集间的邻接矩阵
+     *
+     * @param matrix
+     * @return
+     */
+    public int[] tsp(float[][] matrix) {
+        this.dist = matrix;
+        pointNum = matrix.length;
+        init();
+
+        if (isAutoNextGeneration) {
+            int i = 0;
+            while (i++ < maxGeneration) {
+                nextGeneration();
+            }
+        }
+        isAutoNextGeneration = false;
+
+        return getBestIndivial();
+    }
+
+    /**
+     * 初始化
+     */
+    private void init() {
+        mutationTimes = 0;
+        currentGeneration = 0;
+        bestIndivial = null;
+        bestDist = 0;
+        currentBestPosition = 0;
+        currentBestDist = 0;
+
+        values = new float[populationSize];
+        fitnessValues = new float[populationSize];
+        roulette = new float[populationSize];
+        population = new int[populationSize][pointNum];
+
+        //initDist(points);
+        // 父代
+        for (int i = 0; i < populationSize; i++) {
+            population[i] = randomIndivial(pointNum);
+        }
+        evaluateBestIndivial();
+    }
+
+    /**
+     * 下一代
+     */
+    public int[] nextGeneration() {
+        currentGeneration++;
+
+        // 选择
+        selection();
+        // 交叉
+        crossover();
+        // 变异
+        mutation();
+        // 评价最好
+        evaluateBestIndivial();
+
+        return getBestIndivial();
+    }
+
+    /**
+     * 选择
+     */
+    private void selection() {
+        int[][] parents = new int[populationSize][pointNum];
+
+        int initnum = 4;
+        parents[0] = population[currentBestPosition]; // 当前种群中最好的个体
+        parents[1] = exchangeMutate(bestIndivial.clone()); // 对最好的个体进行交换变异
+        parents[2] = insertMutate(bestIndivial.clone()); // 对最好的个体进行插入变异
+        parents[3] = bestIndivial.clone(); // 所有代中最好的个体
+
+        setRoulette();
+        for (int i = initnum; i < populationSize; i++) {
+            parents[i] = population[wheelOut((int) Math.random())];
+        }
+        population = parents;
+    }
+
+    /**
+     *
+     */
+    private void setRoulette() {
+        //calculate all the fitness
+        for (int i = 0; i < values.length; i++) {
+            fitnessValues[i] = 1.0f / values[i]; // 适应度为路径长的导数
+        }
+
+        //set the roulette
+        float sum = 0;
+        for (int i = 0; i < fitnessValues.length; i++) {
+            sum += fitnessValues[i];
+        }
+        for (int i = 0; i < roulette.length; i++) {
+            roulette[i] = fitnessValues[i] / sum;
+        }
+        for (int i = 1; i < roulette.length; i++) {
+            roulette[i] += roulette[i - 1];
+        }
+    }
+
+    /**
+     * 模拟转盘,进行子代选取
+     *
+     * @param ran
+     * @return
+     */
+    private int wheelOut(int ran) {
+        for (int i = 0; i < roulette.length; i++) {
+            if (ran <= roulette[i]) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+
+    /**
+     * 交换变异
+     *
+     * @param seq
+     * @return
+     */
+    private int[] exchangeMutate(int[] seq) {
+        mutationTimes++;
+        int m, n;
+        do {
+            m = random(seq.length - 2);
+            n = random(seq.length);
+        } while (m >= n);
+
+        int j = (n - m + 1) >> 1;
+        for (int i = 0; i < j; i++) {
+            int tmp = seq[m + i];
+            seq[m + i] = seq[n - i];
+            seq[n - i] = tmp;
+        }
+        return seq;
+    }
+
+    /**
+     * 插入变异
+     *
+     * @param seq
+     * @return
+     */
+    private int[] insertMutate(int[] seq) {
+        mutationTimes++;
+        int m, n;
+        do {
+            m = random(seq.length >> 1);
+            n = random(seq.length);
+        } while (m >= n);
+
+        int[] s1 = Arrays.copyOfRange(seq, 0, m);
+        int[] s2 = Arrays.copyOfRange(seq, m, n);
+
+        for (int i = 0; i < m; i++) {
+            seq[i + n - m] = s1[i];
+        }
+        for (int i = 0; i < n - m; i++) {
+            seq[i] = s2[i];
+        }
+        return seq;
+    }
+
+    /**
+     * 交叉
+     */
+    private void crossover() {
+        int[] queue = new int[populationSize];
+        int num = 0;
+        for (int i = 0; i < populationSize; i++) {
+            if (Math.random() < crossoverProbability) {
+                queue[num] = i;
+                num++;
+            }
+        }
+        queue = Arrays.copyOfRange(queue, 0, num);
+        queue = shuffle(queue);
+        for (int i = 0; i < num - 1; i += 2) {
+            doCrossover(queue[i], queue[i + 1]);
+        }
+    }
+
+    private void doCrossover(int x, int y) {
+        population[x] = getChild(x, y, PREVIOUS);
+        population[y] = getChild(x, y, NEXT);
+    }
+
+    /**
+     * 根据父代求子代
+     *
+     * @param x
+     * @param y
+     * @param pos
+     * @return
+     */
+    private int[] getChild(int x, int y, int pos) {
+        int[] solution = new int[pointNum];
+        int[] px = population[x].clone();
+        int[] py = population[y].clone();
+
+        int dx = 0, dy = 0;
+        int c = px[random(px.length)];
+        solution[0] = c;
+
+        for (int i = 1; i < pointNum; i++) {
+            int posX = indexOf(px, c);
+            int posY = indexOf(py, c);
+
+            if (pos == PREVIOUS) {
+                dx = px[(posX + px.length - 1) % px.length];
+                dy = py[(posY + py.length - 1) % py.length];
+            } else if (pos == NEXT) {
+                dx = px[(posX + px.length + 1) % px.length];
+                dy = py[(posY + py.length + 1) % py.length];
+            }
+
+            for (int j = posX; j < px.length - 1; j++) {
+                px[j] = px[j + 1];
+            }
+            px = Arrays.copyOfRange(px, 0, px.length - 1);
+            for (int j = posY; j < py.length - 1; j++) {
+                py[j] = py[j + 1];
+            }
+            py = Arrays.copyOfRange(py, 0, py.length - 1);
+
+            c = dist[c][dx] < dist[c][dy] ? dx : dy;
+
+            solution[i] = c;
+        }
+        return solution;
+    }
+
+    /**
+     * 变异
+     */
+    private void mutation() {
+        for (int i = 0; i < populationSize; i++) {
+            if (Math.random() < mutationProbability) {
+                if (Math.random() > 0.5) {
+                    population[i] = insertMutate(population[i]);
+                } else {
+                    population[i] = exchangeMutate(population[i]);
+                }
+                i--;
+            }
+        }
+    }
+
+    /**
+     * 评估最好的个体
+     */
+    private void evaluateBestIndivial() {
+        for (int i = 0; i < population.length; i++) {
+            values[i] = calculateIndivialDist(population[i]);
+        }
+        evaluateBestCurrentDist();
+        if (bestDist == 0 || bestDist > currentBestDist) {
+            bestDist = currentBestDist;
+            bestIndivial = population[currentBestPosition].clone();
+        }
+    }
+
+    /**
+     * 计算个体的距离
+     *
+     * @return
+     */
+    private float calculateIndivialDist(int[] indivial) {
+        float sum = dist[indivial[0]][indivial[indivial.length - 1]];
+        for (int i = 1; i < indivial.length; i++) {
+            sum += dist[indivial[i]][indivial[i - 1]];
+        }
+        return sum;
+    }
+
+    /**
+     * 评估得到最短距离
+     */
+    public void evaluateBestCurrentDist() {
+        currentBestDist = values[0];
+        for (int i = 1; i < populationSize; i++) {
+            if (values[i] < currentBestDist) {
+                currentBestDist = values[i];
+                currentBestPosition = i;
+            }
+        }
+    }
+
+
+    /**
+     * 产生个体(乱序)
+     *
+     * @param n
+     * @return
+     */
+    private int[] randomIndivial(int n) {
+        int[] a = new int[n];
+        for (int i = 0; i < n; i++) {
+            a[i] = i;
+        }
+
+        return shuffle(a);
+    }
+
+    /**
+     * 乱序处理
+     *
+     * @param a
+     * @return
+     */
+    private int[] shuffle(int[] a) {
+        for (int i = 0; i < a.length; i++) {
+            int p = random(a.length);
+            int tmp = a[i];
+            a[i] = a[p];
+            a[p] = tmp;
+        }
+        return a;
+    }
+
+    private int random(int n) {
+        Random ran = rd;
+        if (ran == null) {
+            ran = new Random();
+        }
+        return ran.nextInt(n);
+    }
+
+    private int[] concatAllArray(int[] first, int[]... rest) {
+        int totalLength = first.length;
+        for (int[] array : rest) {
+            totalLength += array.length;
+        }
+        int[] result = Arrays.copyOf(first, totalLength);
+        int offset = first.length;
+        for (int[] array : rest) {
+            System.arraycopy(array, 0, result, offset, array.length);
+            offset += array.length;
+        }
+        return result;
+    }
+
+    private int indexOf(int[] a, int index) {
+        for (int i = 0; i < a.length; i++) {
+            if (a[i] == index) {
+                return i;
+            }
+        }
+        return 0;
+    }
+
+    public int[] getBestIndivial() {
+        int[] best = new int[bestIndivial.length];
+        int pos = indexOf(bestIndivial, 0);
+
+        for (int i = 0; i < best.length; i++) {
+            best[i] = bestIndivial[(i + pos) % bestIndivial.length];
+        }
+        return best;
+    }
+
+    public float getBestDist() {
+        return bestDist;
+    }
+
+    public void setMaxGeneration(int maxGeneration) {
+        this.maxGeneration = maxGeneration;
+    }
+
+    public void setAutoNextGeneration(boolean autoNextGeneration) {
+        isAutoNextGeneration = autoNextGeneration;
+    }
+
+    public int getMutationTimes() {
+        return mutationTimes;
+    }
+
+    public int getCurrentGeneration() {
+        return currentGeneration;
+    }
+}

+ 63 - 0
app/src/main/java/com/onlylemi/mapview/library/utils/math/TSPNearestNeighbour.java

@@ -0,0 +1,63 @@
+package com.onlylemi.mapview.library.utils.math;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+
+public class TSPNearestNeighbour {
+
+    private static final float INF = Float.MAX_VALUE;
+
+    private int numberOfNodes;
+    private Deque<Integer> stack;
+    private List<Integer> list;
+
+    public TSPNearestNeighbour() {
+        stack = new ArrayDeque<>();
+        list = new ArrayList<>();
+    }
+
+    public static TSPNearestNeighbour getInstance() {
+        return TSPNearestNeighbourHolder.instance;
+    }
+    private static class TSPNearestNeighbourHolder {
+        private static TSPNearestNeighbour instance = new TSPNearestNeighbour();
+
+    }
+
+    public List<Integer> tsp(float[][] matrix) {
+        numberOfNodes = matrix[0].length;
+        int[] visited = new int[numberOfNodes];
+        visited[0] = 1;
+        stack.push(0);
+        int element, dst = 0, i;
+        boolean minFlag = false;
+
+        // System.out.print(0 + "\t");
+        list.add(0);
+        while (!stack.isEmpty()) {
+            element = stack.peek();
+            i = 0;
+            float min = INF;
+            while (i < numberOfNodes) {
+                if (matrix[element][i] < INF && visited[i] == 0 && min > matrix[element][i]) {
+                    min = matrix[element][i];
+                    dst = i;
+                    minFlag = true;
+                }
+                i++;
+            }
+            if (minFlag) {
+                visited[dst] = 1;
+                stack.push(dst);
+                // System.out.print(dst + "\t");
+                list.add(dst);
+                minFlag = false;
+                continue;
+            }
+            stack.pop();
+        }
+        return list;
+    }
+}

BIN
app/src/main/res/mipmap-hdpi/end_point.png


BIN
app/src/main/res/mipmap-hdpi/mark_touch.png


BIN
app/src/main/res/mipmap-hdpi/start_point.png


BIN
app/src/main/res/mipmap-xhdpi/compass.png


BIN
app/src/main/res/mipmap-xhdpi/mark.png