测试地图最短路径搜索(一):把所有的辅助条件都铺到地图上

 

工具点此链接。

点击这里看下前面介绍的这种方案的基本原理。

测试地图最短路径搜索(一):把所有的辅助条件都铺到地图上

测试地图最短路径搜索(二):换个稍微复杂点的路径模板

测试地图最短路径搜索(三):用更细的路径模板图片来测试

测试地图最短路径搜索(四):做一个稍微完整的手绘覆盖以及路径搜索(附所有代码)

测试地图最短路径搜索(五):整个流程走一遍

 

 

这种最短路径实现方案,目前还有很多缺点。首先是路径图片上,如果画出的路很窄,好像最终结果就找不到路了。估计原因是grid中的判断点密度(40*40)比较小,部分区域的路径,恰好没有“路过”任何一个判断点,导致路径中断。现在(20190209)我也不知道这种方法能不能最终实现。

这里我做一下测试,看看搜索出的最短路径是否符合规则,显示结果看上去有点意思。因为要在地图显示1600个圆点,显示速度会非常慢。后面附上所有代码。

基本实现步骤是这样:

1、制作一个最简单的灰度图,里面有一个矩形障碍物。

2、用切片工具的配准法,生成这个图片的切片供腾讯地图调用。

3、用切片工具——实验室,生成障碍物(或者说路径)矢量数据,数据保存在输出路径中的astar.js文件里面。

4、生成的腾讯地图页面,具备了以下功能:调用切片;显示最短路径,左键定义起点,右键定义终点;障碍物中平铺红色圆点,路径中平铺绿色圆点。

障碍物灰度图是这样的:

选择的腾讯地图区域是在博鳌,点密度40。这个点密度是可以在astar.js文件调整的,太大了显示会非常慢。下面的图可以看到,障碍物切片(就是黑色的那部分)显示正确,红色点基本与障碍物重合,显示正确。绿色点与透明区域重合,是可以作为路径的地方,也是正确的。

根据前面定的规则,路径只能经过绿点,不能经过红点。下面的图是随便搜索出的几条路径。

 

 

 

以上进行的测试让人觉得,用这个方案可以实现地图上最短路径搜索。

map_tx.html

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
    <title>tengxun</title>
    <style type="text/css">
        * {
            margin: 0px;
            padding: 0px;
        }

        body, button, input, select, textarea {
            font: 12px/16px Verdana, Helvetica, Arial, sans-serif;
        }

        p {
            width: 603px;
            padding-top: 3px;
            overflow: hidden;
        }

        .btn {
            width: 100px;
            height: 26px
        }
        #container {
           min-width:603px;
           min-height:767px;
        }
    </style>
    <script charset="utf-8" src="http://map.qq.com/api/js?v=2.exp&key=你自己的key"></script>
		<!--#astarjs_t#-->
	<script type='text/javascript' src='astar0206.js'></script>
	<!--#astarjs_b#-->
    <script>

		//varstar_t
	var graphDiagonal = new Graph(gridDest, { diagonal: true }); 
	var start = graphDiagonal.grid[0][0];
	var end = graphDiagonal.grid[0][3]; 
	var startLng, startLat, endLng, endLat;
	var pathPoi = [], pathLineArray = [];
	console.log('gridBlack length:' + gridBlack.length)
	console.log('gridWhite length:' + gridWhite.length)
	//varstar_b


        var map;
        function init() {
            var earthlayer = new qq.maps.ImageMapType({
                name: 'tentxun',
                alt: 'tentxun',
                tileSize: new qq.maps.Size(256, 256),
                minZoom: 14,
                maxZoom: 16,
                opacity: 1,
                getTileUrl: function (tile, zoom) {
                    var z = zoom,
                            x = tile.x,
                            y = tile.y;
					return 'tiles/' + z + '/' + x + '_' + y + '.png';
                }

            });

            map = new qq.maps.Map(document.getElementById("container"), {
                center: new qq.maps.LatLng(19.13446, 110.56323),
                zoom: 15
            });
            map.overlayMapTypes.push(earthlayer);

			// 测试 圆点 -------------------
			for (var i=0; i<gridBlack.length ; i++)
			{
				for (var j=0; j<gridBlack[i].length ; j++)
				{
					//console.log(gridBlack[i][j][0] + '---' + gridBlack[i][j][1]);
					//console.log('gridBlack['+i+']:' + gridBlack[i].length);
					var center=new qq.maps.LatLng(gridBlack[i][j][1], gridBlack[i][j][0]);

					var circle=new qq.maps.Circle({
						map:map,
						center:center,
						radius:12,
						fillColor:"#F00",
						strokeWeight:0
					});
				}
			}

			for (var i=0; i<gridWhite.length ; i++)
			{
				//console.log('gridWhite['+i+']:' + gridWhite[i].length);
				for (var j=0; j<gridWhite[i].length ; j++)
				{
					//console.log(gridWhite[i][j][0] + '---' + gridWhite[i][j][1]);
					var center=new qq.maps.LatLng(gridWhite[i][j][1], gridWhite[i][j][0]);

					var circle=new qq.maps.Circle({
						map:map,
						center:center,
						radius:12,
						fillColor:"#0F0",
						strokeWeight:0
					});
				}
			}

			// 测试 圆点 ====================

			//click_t
			  //添加监听事件
			  qq.maps.event.addListener(map, 'click', function(event) {
				var latLng = event.latLng;
				var lng = latLng.getLng();
				var lat = latLng.getLat();
				var a = lngToGC(lng, zoomStar);
				var b = latToGR(lat, zoomStar);
				startLng = lng;
				startLat = lat;
				start = graphDiagonal.grid[a][b];
				drawLine();
			  });
			  qq.maps.event.addListener(map, 'rightclick', function(event) {
				var latLng = event.latLng;
				var lng = latLng.getLng();
				var lat = latLng.getLat();
				var a = lngToGC(lng, zoomStar);
				var b = latToGR(lat, zoomStar);
				endLng = lng;
				endLat = lat;
				end = graphDiagonal.grid[a][b];
				drawLine(); 
			  });
			  //click_b 



        }
		//drawline_t
         function drawLine(){
			 var sTime = performance ? performance.now() : new Date().getTime();
         	pathlnla = astar.search(graphDiagonal, start, end, { heuristic: astar.heuristics.diagonal });

			var fTime = performance ? performance.now() : new Date().getTime(),
				duration = (fTime-sTime).toFixed(2);
			console.log('time:' + duration);


         	pathPoi = [];
         	for (var i=0; i<pathlnla.length ; i++)
         	{
         		var s1 = pathlnla[i];
         		var regx = /(?<=\[)\d+\.\d+/;
         		var regy = /\d+\.\d+(?=\])/;
         		var pathPoiX = regx.exec(s1);
         		var pathPoiY = regy.exec(s1);
         		var poi = new qq.maps.LatLng(pathPoiY, pathPoiX);
         		pathPoi.push(poi);
         	}
         	if (pathLineArray) {
         		for (i in pathLineArray) {
         			pathLineArray[i].setMap(null);
         		}
         	}
         	var polyline = new qq.maps.Polyline({
         		path: pathPoi,
         		strokeColor: '#FF0000',
         		strokeWeight: 5,
         		editable:false,
         		map: map
         	});
         	pathLineArray.push(polyline);
         }
         //drawline_b

    </script>
</head>
<body onload="init();">
<div id="container"></div>
<span id="info"></span>
</body>
</html>

astar0206.js

// javascript-astar 0.4.1
// http://github.com/bgrins/javascript-astar
// Freely distributable under the MIT License.
// Implements the astar search algorithm in javascript using a Binary Heap.
// Includes Binary Heap (with modifications) from Marijn Haverbeke.
// http://eloquentjavascript.net/appendix2.html
(function(definition) {
  /* global module, define */
  if (typeof module === 'object' && typeof module.exports === 'object') {
    module.exports = definition();
  } else if (typeof define === 'function' && define.amd) {
    define([], definition);
  } else {
    var exports = definition();
    window.astar = exports.astar;
    window.Graph = exports.Graph;
	window.gridDest = exports.gridDest;
	window.lngToGC = exports.lngToGC;
	window.latToGR = exports.latToGR;
	window.zoomStar = exports.zoomStar;
	window.gridBlack = exports.gridBlack;
	window.gridWhite = exports.gridWhite;
	//window.startR = exports.startR; 
	//window.startC = exports.startC;

  }
})(function() {

function pathTo(node) {
  var curr = node;
  var path = [];
  var pathLngLat = []; // cbq
  while (curr.parent) {
    path.unshift(curr);
    curr = curr.parent;
  }
  console.log('path.length:' + path.length);
	var pathLngLat = [];
	for (var i = 0; i < path.length; i++) {
		var s1 = path[i];
		var regx = /(?<=\[)\d+/;
		var regy = /\d+(?=\])/;
		var pathPoiX = regx.exec(s1);
		var pathPoiY = regy.exec(s1);
		var lng = pixToLng(deltX + cellWid * (pathPoiX), zoomStar);  // polyPoiF[i].x + deltaLenX
		var lat = pixToLat(deltY + cellWid * (pathPoiY), zoomStar);  // polyPoiF[i].y + deltaLenY
		pathLngLat[i] = '[' + lng + ',' + lat + ']';
	}
	return pathLngLat;
}

function getHeap() {
  return new BinaryHeap(function(node) {
    return node.f;
  });
}

//---------------------------------------------------------------------------------
var	
	deltX = 13540482,
	deltY = 7478025,
	zoomStar = 16,
	w = 2587,
	c = 40;

	var 
	cellWid = w/c, 
	cellPosX, cellPosY, //, startR, startC;
	gridBlack = [], gridWhite = [];

var poly0 = [[816,1867.703125],[818,431],[1132,431],[1130,1869],[817.296875,1869],];

	function inside(point, vs) {
		var x = point[0], y = point[1];
		var res = false;
		for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
			var xi = vs[i][0], yi = vs[i][1];
			var xj = vs[j][0], yj = vs[j][1];
			
			var intersect = ((yi > y) != (yj > y))
				&& (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
			if (intersect) res = !res;
		}
		return res;
	};

	function pixToLng(pixelX, zoom){
		return pixelX * 360 / (256 * Math.pow(2, zoom)) - 180; 
	}

	function lngToGC(lng, zoom){
		var f = (lng + 180) * (256 * Math.pow(2, zoom)) / 360;
		var r = (f - deltX + cellWid / 2) / cellWid;

		if (r > c - 1)
		{
		  r = c - 1;
		}else if(r < 0)
		{
		  r = 0;
		}

		return Math.floor(r);
	}

	function pixToLat(pixelY, zoom){
		var  siny, y, z, E;
		E = 2.71828182845904523536;
		y = 2 * Math.PI * (1 - pixelY / (128 * Math.pow(2, zoom)));
		z = Math.pow(E, y);
		siny = (z - 1) / (z + 1);
		return Math.asin(siny) * 180 / Math.PI;    
	}

	function latToGR(lat, zoom){
		  var siny = Math.sin(lat * Math.PI / 180);
		  var y = Math.log((1 + siny) / (1 - siny));
		  var f = (128 * Math.pow(2, zoom)) * (1 - y / (2 * Math.PI));
		  var r = (f - deltY + cellWid / 2) / cellWid;
		  if (r > c - 1)
		  {
			  r = c - 1;
		  }else if(r < 0)
		  {
			  r = 0;
		  }

		  return Math.floor(r);
	}

	var gridDest = [];
	
	for (var y = 0; y < c; y++ )
	{
		var gridRow = [];
		var gridRowBlack = [];
		var gridRowWhite = [];

		for (var x = 0; x < c; x++ )
		{
			var isWall = 1;
			var a1 = [];
			
			cellPosX = cellWid * x; 
			cellPosY = cellWid * y;
			
			//if((inside([cellPosX, cellPosY], poly0)) || (inside([cellPosX, cellPosY], poly1)) || (inside([cellPosX, cellPosY], poly2)) || (inside([cellPosX, cellPosY], poly3)) || (inside([cellPosX, cellPosY], poly4)) || (inside([cellPosX, cellPosY], poly5)) || (inside([cellPosX, cellPosY], poly6)) || (inside([cellPosX, cellPosY], poly7)) || (inside([cellPosX, cellPosY], poly8))){
			if((inside([cellPosX, cellPosY], poly0))){
				isWall = 0;
				var lng = pixToLng(deltX + cellWid * (y), zoomStar);  // polyPoiF[i].x + deltaLenX
				var lat = pixToLat(deltY + cellWid * (x), zoomStar);  // polyPoiF[i].y + deltaLenY
				a1.push(lng);
				a1.push(lat);
				
				gridRowBlack.push(a1);

			}else{
				isWall = 1;
				var lng = pixToLng(deltX + cellWid * (y), zoomStar);  // polyPoiF[i].x + deltaLenX
				var lat = pixToLat(deltY + cellWid * (x), zoomStar);  // polyPoiF[i].y + deltaLenY
				a1.push(lng);
				a1.push(lat);

				//gridRowWhite.push('[' + lng + '],[' + lat + ']');
				gridRowWhite.push(a1);
			}
			gridRow.push(isWall);


		}
		gridDest.push(gridRow);
		gridBlack.push(gridRowBlack);
		gridWhite.push(gridRowWhite);

	}
//=====================================================================================

var astar = {
  /**
  * Perform an A* Search on a graph given a start and end node.
  * @param {Graph} graph
  * @param {GridNode} start
  * @param {GridNode} end
  * @param {Object} [options]
  * @param {bool} [options.closest] Specifies whether to return the
             path to the closest node if the target is unreachable.
  * @param {Function} [options.heuristic] Heuristic function (see
  *          astar.heuristics).
  */
  search: function(graph, start, end, options) {
    graph.cleanDirty();
    options = options || {};
    var heuristic = options.heuristic || astar.heuristics.manhattan;
    var closest = options.closest || false;

    var openHeap = getHeap();
    var closestNode = start; // set the start node to be the closest if required

    start.h = heuristic(start, end);
    graph.markDirty(start);

    openHeap.push(start);

    while (openHeap.size() > 0) {

      // Grab the lowest f(x) to process next.  Heap keeps this sorted for us.
      var currentNode = openHeap.pop();

      // End case -- result has been found, return the traced path.
      if (currentNode === end) {
        return pathTo(currentNode);
      }

      // Normal case -- move currentNode from open to closed, process each of its neighbors.
      currentNode.closed = true;

      // Find all neighbors for the current node.
      var neighbors = graph.neighbors(currentNode);

      for (var i = 0, il = neighbors.length; i < il; ++i) {
        var neighbor = neighbors[i];

        if (neighbor.closed || neighbor.isWall()) {
          // Not a valid node to process, skip to next neighbor.
          continue;
        }

        // The g score is the shortest distance from start to current node.
        // We need to check if the path we have arrived at this neighbor is the shortest one we have seen yet.
        var gScore = currentNode.g + neighbor.getCost(currentNode);
        var beenVisited = neighbor.visited;

        if (!beenVisited || gScore < neighbor.g) {

          // Found an optimal (so far) path to this node.  Take score for node to see how good it is.
          neighbor.visited = true;
          neighbor.parent = currentNode;
          neighbor.h = neighbor.h || heuristic(neighbor, end);
          neighbor.g = gScore;
          neighbor.f = neighbor.g + neighbor.h;
          graph.markDirty(neighbor);
          if (closest) {
            // If the neighbour is closer than the current closestNode or if it's equally close but has
            // a cheaper path than the current closest node then it becomes the closest node
            if (neighbor.h < closestNode.h || (neighbor.h === closestNode.h && neighbor.g < closestNode.g)) {
              closestNode = neighbor;
            }
          }

          if (!beenVisited) {
            // Pushing to heap will put it in proper place based on the 'f' value.
            openHeap.push(neighbor);
          } else {
            // Already seen the node, but since it has been rescored we need to reorder it in the heap
            openHeap.rescoreElement(neighbor);
          }
        }
      }
    }

    if (closest) {
      return pathTo(closestNode);
    }

    // No result was found - empty array signifies failure to find path.
    return [];
  },
  // See list of heuristics: http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html
  heuristics: {
    manhattan: function(pos0, pos1) {
      var d1 = Math.abs(pos1.x - pos0.x);
      var d2 = Math.abs(pos1.y - pos0.y);
      return d1 + d2;
    },
    diagonal: function(pos0, pos1) {
      var D = 1;
      var D2 = Math.sqrt(2);
      var d1 = Math.abs(pos1.x - pos0.x);
      var d2 = Math.abs(pos1.y - pos0.y);
      return (D * (d1 + d2)) + ((D2 - (2 * D)) * Math.min(d1, d2));
    }
  },
  cleanNode: function(node) {
    node.f = 0;
    node.g = 0;
    node.h = 0;
    node.visited = false;
    node.closed = false;
    node.parent = null;
  }
};

/**
 * A graph memory structure
 * @param {Array} gridIn 2D array of input weights
 * @param {Object} [options]
 * @param {bool} [options.diagonal] Specifies whether diagonal moves are allowed
 */
function Graph(gridIn, options) {
  options = options || {};
  this.nodes = [];
  this.diagonal = !!options.diagonal;
  this.grid = [];
  for (var x = 0; x < gridIn.length; x++) {
    this.grid[x] = [];

    for (var y = 0, row = gridIn[x]; y < row.length; y++) {
      var node = new GridNode(x, y, row[y]);
      this.grid[x][y] = node;
      this.nodes.push(node);
    }
  }
  this.init();
}

Graph.prototype.init = function() {
  this.dirtyNodes = [];
  for (var i = 0; i < this.nodes.length; i++) {
    astar.cleanNode(this.nodes[i]);
  }
};

Graph.prototype.cleanDirty = function() {
  for (var i = 0; i < this.dirtyNodes.length; i++) {
    astar.cleanNode(this.dirtyNodes[i]);
  }
  this.dirtyNodes = [];
};

Graph.prototype.markDirty = function(node) {
  this.dirtyNodes.push(node);
};

Graph.prototype.neighbors = function(node) {
  var ret = [];
  var x = node.x;
  var y = node.y;
  var grid = this.grid;

  // West
  if (grid[x - 1] && grid[x - 1][y]) {
    ret.push(grid[x - 1][y]);
  }

  // East
  if (grid[x + 1] && grid[x + 1][y]) {
    ret.push(grid[x + 1][y]);
  }

  // South
  if (grid[x] && grid[x][y - 1]) {
    ret.push(grid[x][y - 1]);
  }

  // North
  if (grid[x] && grid[x][y + 1]) {
    ret.push(grid[x][y + 1]);
  }

  if (this.diagonal) {
    // Southwest
    if (grid[x - 1] && grid[x - 1][y - 1]) {
      ret.push(grid[x - 1][y - 1]);
    }

    // Southeast
    if (grid[x + 1] && grid[x + 1][y - 1]) {
      ret.push(grid[x + 1][y - 1]);
    }

    // Northwest
    if (grid[x - 1] && grid[x - 1][y + 1]) {
      ret.push(grid[x - 1][y + 1]);
    }

    // Northeast
    if (grid[x + 1] && grid[x + 1][y + 1]) {
      ret.push(grid[x + 1][y + 1]);
    }
  }

  return ret;
};

Graph.prototype.toString = function() {
  var graphString = [];
  var nodes = this.grid;
  for (var x = 0; x < nodes.length; x++) {
    var rowDebug = [];
    var row = nodes[x];
    for (var y = 0; y < row.length; y++) {
      rowDebug.push(row[y].weight);
    }
    graphString.push(rowDebug.join(" "));
  }
  return graphString.join("\n");
};

function GridNode(x, y, weight) {
  this.x = x;
  this.y = y;
  this.weight = weight;
}

GridNode.prototype.toString = function() {
  return "[" + this.x + " " + this.y + "]";
};

GridNode.prototype.getCost = function(fromNeighbor) {
  // Take diagonal weight into consideration.
  if (fromNeighbor && fromNeighbor.x != this.x && fromNeighbor.y != this.y) {
    return this.weight * 1.41421;
  }
  return this.weight;
};

GridNode.prototype.isWall = function() {
  return this.weight === 0;
};

function BinaryHeap(scoreFunction) {
  this.content = [];
  this.scoreFunction = scoreFunction;
}

BinaryHeap.prototype = {
  push: function(element) {
    // Add the new element to the end of the array.
    this.content.push(element);

    // Allow it to sink down.
    this.sinkDown(this.content.length - 1);
  },
  pop: function() {
    // Store the first element so we can return it later.
    var result = this.content[0];
    // Get the element at the end of the array.
    var end = this.content.pop();
    // If there are any elements left, put the end element at the
    // start, and let it bubble up.
    if (this.content.length > 0) {
      this.content[0] = end;
      this.bubbleUp(0);
    }
    return result;
  },
  remove: function(node) {
    var i = this.content.indexOf(node);

    // When it is found, the process seen in 'pop' is repeated
    // to fill up the hole.
    var end = this.content.pop();

    if (i !== this.content.length - 1) {
      this.content[i] = end;

      if (this.scoreFunction(end) < this.scoreFunction(node)) {
        this.sinkDown(i);
      } else {
        this.bubbleUp(i);
      }
    }
  },
  size: function() {
    return this.content.length;
  },
  rescoreElement: function(node) {
    this.sinkDown(this.content.indexOf(node));
  },
  sinkDown: function(n) {
    // Fetch the element that has to be sunk.
    var element = this.content[n];

    // When at 0, an element can not sink any further.
    while (n > 0) {

      // Compute the parent element's index, and fetch it.
      var parentN = ((n + 1) >> 1) - 1;
      var parent = this.content[parentN];
      // Swap the elements if the parent is greater.
      if (this.scoreFunction(element) < this.scoreFunction(parent)) {
        this.content[parentN] = element;
        this.content[n] = parent;
        // Update 'n' to continue at the new position.
        n = parentN;
      }
      // Found a parent that is less, no need to sink any further.
      else {
        break;
      }
    }
  },
  bubbleUp: function(n) {
    // Look up the target element and its score.
    var length = this.content.length;
    var element = this.content[n];
    var elemScore = this.scoreFunction(element);

    while (true) {
      // Compute the indices of the child elements.
      var child2N = (n + 1) << 1;
      var child1N = child2N - 1;
      // This is used to store the new position of the element, if any.
      var swap = null;
      var child1Score;
      // If the first child exists (is inside the array)...
      if (child1N < length) {
        // Look it up and compute its score.
        var child1 = this.content[child1N];
        child1Score = this.scoreFunction(child1);

        // If the score is less than our element's, we need to swap.
        if (child1Score < elemScore) {
          swap = child1N;
        }
      }

      // Do the same checks for the other child.
      if (child2N < length) {
        var child2 = this.content[child2N];
        var child2Score = this.scoreFunction(child2);
        if (child2Score < (swap === null ? elemScore : child1Score)) {
          swap = child2N;
        }
      }

      // If the element needs to be moved, swap it, and continue.
      if (swap !== null) {
        this.content[n] = this.content[swap];
        this.content[swap] = element;
        n = swap;
      }
      // Otherwise, we are done.
      else {
        break;
      }
    }
  }
};

return {
  astar: astar,
  Graph: Graph,
  gridDest: gridDest,
  lngToGC: lngToGC,
  latToGR: latToGR,
  zoomStar: zoomStar,
  gridBlack: gridBlack,
  gridWhite: gridWhite
};

});

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值