An overview of the

Recently, I met a requirement in my project: to realize the function of circle house search in homelink map. Homelink uses Baidu Map to display housing resources. First, let’s have a look at the function of homelink circle housing search. Is it cool






In the project to circle the effect of looking for a room









Why write this article

I would like to share my thinking process and how to solve some problems encountered in the process of completing the function of circle house search when there is no ready-made API call or solution

Step 0: Prepare

Open your favorite IDE and create the following 3 files: draw.js, draw.css, draw.html. Here I use Webstorm to edit the code. The demo structure is shown below

draw.html,draw.css


      
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Chain home network painting circle housing demo</title>
    <link href="draw.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<div class="wrapper">
    <div class="map-container" id="container">
    </div>
    <div class="panel">
        <div class="top">
            <button class="btn" id="draw">Circle to find room</button>
            <button class="btn" id="exit">Out of a circle</button>
        </div>
        <div class="bottom">
            <ul id="data">
            </ul>
        </div>
    </div>
</div>
<script type="text/javascript" src="draw.js"></script>
</body>
</html>
Copy the code
html.body{
    margin:0;
    padding:0;
    height:100%;
    min-width:800px;
}
ul.li{
    margin:0;
    padding:0;
}
.wrapper{
    height:100%;
    padding-right:300px;
}
.map-container{
    height:100%;
    width:100%;
    float:left;
}
.panel{
    float:left;
    margin-left: -300px;
    width:300px;
    height:100%;
    position: relative;
    right: -300px;
    box-shadow: -2px 2px 2px #d9d9d9;
}
.top{
    height:150px;
    padding:10px;
    border-bottom: 1px solid #bfbfbf;
}
.bottom{
    position: absolute;
    top:171px;
    bottom:0;
    width:100%;
}
.btn{
    outline:none;
    border:none;
    display: block;
    margin: 20px auto;
    font-size: 17px;
    color:#fff;
    border-radius: 4px;
    padding:8px;
    background-color: # 969696;
    cursor:pointer;
    transition: all .5s;
}
.btn:hover{
    background-color: #b8b8b8;
}

#data li {
    width:100%;
    height:50px;
    border-bottom: 1px dashed #bfbfbf;
    padding:10px 20px;
    list-style-type: none;
    line-height: 50px;
    color: # 737373;
}
Copy the code

The left container is used to display Baidu map, and the right is the operation panel. The page is as shown in the picture below. Click the circle to find a room to enter the circle state, click the exit circle button to enter the normal map operation state, and the data list is displayed below the button


Step 1: Initialize Baidu Map

This demo needs map support. Baidu Map is used here. Tencent Map and Autonavi Map should also be able to achieve the effect of this demo. First, log in to baidu Map open platform for account registration. If you have a Baidu account, you don’t need to register it. Before using Baidu Map service, you need to apply for a key (AK).

Then add baidu map script to draw. HTML, and then change the Chinese behind AK into the key you just applied for

<script type="text/javascript" src="Http://api.map.baidu.com/api?v=2.0&ak= your key">
Copy the code

Finally write the following code in draw.js, Baidu map initialization is finished, run draw.html can see the map has been displayed (see the picture below)! Container in the first sentence of the code is the ID of the map container. We choose Beijing as the display coordinate

window.onload = function(){
    var map = new BMap.Map("container");          // Create a map instance
    var point = new BMap.Point(116.404.39.915);  // Create point coordinates
    map.centerAndZoom(point, 15);                 // Initialize the map, set the center point coordinates and map level
    map.enableScrollWheelZoom(true);              // Enable mouse wheel zooming
}
Copy the code





Step 2: Event binding and point data placement

We need to place several points on the map as initial data for drawing circles, refer to baidu map API, write the following function in draw.js to initialize, and call this method in window.onload

// Initialize map coordinates
function initMapMarkers(map){
    // Coordinates of points to be marked on the map (longitude, latitude, text description)
    var dataList = [
    	[116.351951.39.929543.Beijing State Guest Hotel],
    	[116.404556.39.92069.'The Palace Museum'],
    	[116.479008.39.925781.Hujialou],
    	[116.368624.39.870869.Capital Medical University],
    	[116.4471.39.849601.'Songjiazhuang']].// Create marker and label and display them on the map
    dataList.forEach(function(item){
    	var point = new BMap.Point(item[0],item[1])
    	var marker = new BMap.Marker(point);
    	var label = new BMap.Label(item[2] and {offset:new BMap.Size(20.- 10)});
    	marker.setLabel(label);
    	markerList.push(marker);
    	map.addOverlay(marker)
    })
}
Copy the code

/*** interface element ***/
// Draw the circle button
var drawBtn = document.getElementById('draw')
// Exit the circle button
var exitBtn = document.getElementById('exit')
// Circle the completed data display list
var ul = document.getElementById('data')

/*** circles related data structures ***/
// Whether to draw a circle
var isInDrawing = false;
// Whether the left mouse button is down
var isMouseDown = false;
// Store an array of dotted points
var polyPointArray = [];
// The polyline drawn in the last operation
var lastPolyLine = null;
// The polygon generated after the circle is completed
var polygonAfterDraw = null;
// Store an array of markers on the map
var markerList = [];
Copy the code

The demo basic logic is as follows: click the button enter the circle circle to find room state, in the condition of circle, circles on the map by pressing the left mouse button to start operation, move the mouse to circle operation, then lift the left mouse button to complete the circle, the map will show ones, then list shows the right ones within the coordinates of the text. Finally, click the Exit circle button to exit the circle state, so you need to bind events for the map and button. Write the following function to bind events:

// Begin to circle the binding event
drawBtn.addEventListener('click'.function(e){
	// Forbid map moving and clicking
	map.disableDragging();
	map.disableScrollWheelZoom();
	map.disableDoubleClickZoom();
	map.disableKeyboard();
	map.setDefaultCursor('crosshair');
	// Set the flag bit to enter the circle state
	isInDrawing = true;
});
// Exit the loop button binding event
exitBtn.addEventListener('click'.function(e){
	// Restore map move click and other operations
	map.enableDragging();
	map.enableScrollWheelZoom();
	map.enableDoubleClickZoom();
	map.enableKeyboard();
	map.setDefaultCursor('default');
	// Set the flag bit to exit the circle state
	isInDrawing = false;
})
Copy the code


Step 3: How to achieve “draw” operation

This is the first difficulty of this demo, how to achieve the homelink painting operation similar to a paintbrush? After looking through all the drawing apis of Baidu Map, I only found that Baidu Map provides apis for drawing circles, lines, polygons and rectangles. The demo on the official website is as follows:

A line segment

Go back to Baidu map API, found that there is an API to draw polylines on the map, its parameter is an array composed of points, as long as the array is given to draw polylines, click here to go to Baidu map API

map.addEventListener('mousemove'.function(e){
    console.log(e.point)
})
Copy the code

Now that the points are available, store them in an array for subsequent line drawing. Then the whole line logic becomes obvious: Each mousemove trigger pushes the current mouse point into the array, then calls the API to draw a line, and records the last line with a lastPolyLine variable. Because each mousemove trigger draws the entire array from beginning to end, we need to erase the last line segment. And then draw new line segments, otherwise the line segments on the map will pile up on top of each other. This allows you to write the following code

map.addEventListener('mousemove'.function(e){
	// If the mouse is pressed down, the drawing operation can be performed
	if(isMouseDown){
		// Add pathpoints collected during mouse movement to array for saving
		polyPointArray.push(e.point);
		// Remove the last line drawn
		if(lastPolyLine) {
			map.removeOverlay(lastPolyLine)
		}
		// Build the drawn polyline from the existing path array
		var polylineOverlay = new window.BMap.Polyline(polyPointArray,{
			strokeColor:'#00ae66'.strokeOpacity:1.enableClicking:false
		});
		// Add new lines to the map
		map.addOverlay(polylineOverlay);
		// Update the last line drawn
		lastPolyLine = polylineOverlay
	}
})
Copy the code

Note one minor detail: you need to set the enableClicking property of the Polyline parameter to false, otherwise the mouse licking will display a clickable icon when you hover over the drawn line segment. Note that the above code does not handle the last drawn line deletion logic, it is placed into the map mousedown event to continue the analysis. When the mouse pointer is raised, it indicates that the line drawing is complete, and a polygon with filled color will be displayed on the map at this time. How to deal with this? Also very simple, Baidu Map provides an API to draw polygons

map.addEventListener('mouseup'.function(e){
	// If the mouse is in the circled state and the mouse is down
	if(isInDrawing && isMouseDown){
		// Exit the drawing state
		isMouseDown = false;
		// Add a polygon overlay and set it to disable clicking
		var polygon = new window.BMap.Polygon(polyPointArray,{
			strokeColor:'#00ae66'.strokeOpacity:1.fillColor:'#00ae66'.fillOpacity:0.3.enableClicking:false
		});
		map.addOverlay(polygon);
		// Save the polygon for subsequent deletion
		polygonAfterDraw = polygon
		// Calculate the house's inclusion of polygons
		var ret = caculateEstateContainedInPolygon(polygonAfterDraw);
		// Update the DOM structure
		ul.innerHTML = ' ';
		var fragment = document.createDocumentFragment();
		for(var i=0; i<ret.length; i++){var li = document.createElement('li'); li.innerText ? li.innerText = ret[i] : li.textContent = ret[i]; fragment.appendChild(li); } ul.appendChild(fragment); }});Copy the code

Polygons have various parameters to set their styles, and the polygons drawn can be very strange, because you can scribble, just like graffiti. The polygons below look illegal but there is no problem, there can be various holes in the middle, the specific logic of this is a matter inside baidu API








Step 4: How to determine the point in any polygon

This is the second difficulty of this demo. After searching online, I found a method called ray method is easier to understand, as shown in the figure below



An odd number:



An even number of:


// Determine whether a point is contained within a polygon
function isPointInPolygon(point,bound,pointArray){
	// First check if the point is in the outer rectangle, if not return false
	if(! bound.containsPoint(point)){return false;
	}
	// If it is inside the outer rectangle, it is further judged
	// The number of intersections between this point and the edge of the rectangle, if odd, is inside the polygon, otherwise outside
	var crossPointNum = 0;
	for(var i=0; i<pointArray.length; i++){// Get two adjacent points
		var p1 = pointArray[i];
		var p2 = pointArray[(i+1)%pointArray.length];
		// LNG is longitude, LAT is latitude
		// Return true if the coordinates of the points are equal
		if((p1.lng===point.lng && p1.lat===point.lat)||(p2.lng===point.lng && p2.lat===point.lat)){
			return true
		}
		// Continue if point is below both points
		if(point.lat < Math.min(p1.lat,p2.lat)){
			continue;
		}
		// Continue if point is above both points
		if(point.lat >= Math.max(p1.lat,p2.lat)){
			continue;
		}
		// There are two intersecting points: one up and one down
		// Special case 2 points have the same abscissa
		var crossPointLng;
		// If the two points x are the same, the slope is infinite, special treatment
		if(p1.lng === p2.lng){
			crossPointLng = p1.lng;
		}else{
			// Calculate the slope of 2 points
			var k = (p2.lat - p1.lat)/(p2.lng - p1.lng);
			// Get the abscissa of the intersection of the line formed by the horizontal ray and these two points
			crossPointLng = (point.lat - p1.lat)/k + p1.lng;
		}
		// If crossPointLng is greater than the x-coordinate of point, then the intersection is calculated.
		if(crossPointLng > point.lng){ crossPointNum++; }}// If there are an odd number of intersections, the points are inside the polygon
	return crossPointNum%2= = =1
}
Copy the code

Bound. containsPoint(point) specifies whether a polygon is contained within a bound.containsPoint(point) specifies whether a polygon is contained within a bounding rectangle. Can reduce the workload Note here judge the position relations of linear code, the first if not equal to the second has the equal sign, there is a special case of this judgment, there is an if any equals sign, and pay attention to the computing intersection location code is easy to write wrong, first line two endpoints can calculate the slope, And then you can figure out the slope of the intersection and one of the endpoints, and the y of the intersection is fixed, so it’s pretty easy to figure out the x of the intersection. So how do you tell if it’s the rays on the right? It’s easy to say that the x value of the intersection is greater than the x value of the coordinate

// Continue if point is below both points
if(point.lat < Math.min(p1.lat,p2.lat)){
	continue;
}
// Continue if point is above both points
if(point.lat >= Math.max(p1.lat,p2.lat)){
	continue;
}
Copy the code

Here, markerList is a global variable. During map initialization, marker instances of all points are recorded. GetPath,getBounds, etc., are API interfaces. Text array

// Calculate the inclusion status of points on the map
function caculateEstateContainedInPolygon(polygon){
	// Get the number of polygons
	var pointArray = polygon.getPath();
	// Get the enclosing rectangle of the polygon
	var bound = polygon.getBounds();
	// An array of points within a polygon
	var pointInPolygonArray = [];
	// Calculate whether each point is contained within the polygon
	for(var i=0; i<markerList.length; i++){// The coordinate point of this marker
		var markerPoint = markerList[i].getPosition();
		if(isPointInPolygon(markerPoint,bound,pointArray)){
			pointInPolygonArray.push(markerList[i])
		}
	}
	var estateListAfterDrawing = pointInPolygonArray.map(function(item){
		return item.getLabel().getContent()
	})
	return estateListAfterDrawing
}

Copy the code

At this point, the whole demo core functions are complete ~~ the right side shows 3 circled coordinate points




conclusion

All the code for this demo is on Github. Click here to enter a pit encountered during the project: The API of Baidu Map is not accurate, such as the API part of Label. At that time, I needed to get the text content of Label according to a Label instance, but the document only had the setContent method to get the document instead of the getContent method. At that time, I was shocked. If you can’t get the text, you can’t do it. Is baidu map written? I tried the getContent() method in the code, and sure enough! You can get the text and no errors, so you can see that the document is not accurate, you need to try more to get accurate results