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