iOS 开发地图常见功能总结(定位、标注大头针、划线、路径规划、导航、地理围栏等)

前言

最近开发的项目使用了一些第三方地图的功能,主要包括:

  • 定位
  • POI(附近,模糊)搜索
  • 地理编码和逆编码
  • 地图展示
  • 标记点(大头针)
  • 线段,图形,覆盖物(折线,直线,圆形,不规则图形)
  • 导航(内部导航,跳转其他地图 App 导航)
  • 地理围栏

主要包含了高德地图、谷歌地图等,其他的地图如百度地图,腾讯地图等参考相关文档其实差别也不大。

文章主要记录一下相关代码和一些集成调试踩过的一些坑,防止忘记,归个档。

废话不多说,先看效果:

<img src="https://image-1254431338.cos.ap-guangzhou.myqcloud.com/2020-11-14-105932.png" alt="image-20201114185932086" style="zoom: 25%;" />

ezgif.com-gif-makereee

<img src="https://image-1254431338.cos.ap-guangzhou.myqcloud.com/2020-11-14-111311.gif" style="zoom: 50%;" />

<img src="https://image-1254431338.cos.ap-guangzhou.myqcloud.com/2020-11-14-110809.png" alt="image-20201114190809301" style="zoom:25%;" />

<img src="https://image-1254431338.cos.ap-guangzhou.myqcloud.com/2020-11-14-114426.png" alt="image-20201114194426235" style="zoom:33%;" />

定位

CoreLocation 框架CLLocationManager 类。

定位 - 系统原生

// 1. 初始化 CLLocationManager 实例
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.distanceFilter = kCLDistanceFilterNone

// 持续定位上报
locationManager.allowsBackgroundLocationUpdates = true
// 是否允许中断定位功能
locationManager.pausesLocationUpdatesAutomatically = false

locationManager.startUpdatingLocation()

// 权限判断
let status  = CLLocationManager.authorizationStatus()
        
if status == .notDetermined {
    locationManager.requestWhenInUseAuthorization()
}

if status == .denied || status == .restricted {
    let alert = UIAlertController(title: "定位权限未开启", message: "请前往设置允许访问您的定位权限", preferredStyle: .alert)
    let okAction = UIAlertAction(title: "去开启", style: .default, handler: { _ in
        // 打开设置
        UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
    })

    alert.addAction(okAction)
    let cancelAction = UIAlertAction(title: "取消", style: .cancel, handler: nil)
    alert.addAction(cancelAction)

    UIApplication.shared.visibleVC().present(alert, animated: true, completion: nil)
    return
}

// 3. 代理回调 CLLocationManagerDelegate
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    let currentLocation = locations.last
    
    // 得到经纬度
    let currentCoordinate2D = currentLocation?.coordinate
    guard let latitude = currentCoordinate2D?.latitude, let longitude = currentCoordinate2D?.longitude else { return }

    // 定位成功后停止定位
    locationManager.stopUpdatingLocation()
}

// 发生错误
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
    
}

定位 - 高德定位

  1. 高德开放平台注册账号
  2. 新建应用
  3. 拿到 key,在 App 启动的时候设置
// 设置 key
AMapServices.shared()?.apiKey = "\(appKey)"


// 1. 初始化 AMapLocationManager 实例
private lazy var locationManager : AMapLocationManager = {
    let managner = AMapLocationManager()
    managner.delegate = self
    managner.pausesLocationUpdatesAutomatically = false
    // 定位精度
    managner.desiredAccuracy = kCLLocationAccuracyBest
    // 是否允许后台定位
    managner.allowsBackgroundLocationUpdates = false
    
    // 指定单次定位超时时间,默认为10s。最小值是2s。注意单次定位请求前设置。注意: 单次定位超时时间从确定了定位权限(非kCLAuthorizationStatusNotDetermined状态)后开始计算。
    manager.locationTimeout = 2
    // 指定单次定位逆地理超时时间,默认为5s。最小值是2s。注意单次定位请求前设置。
    manager.reGeocodeTimeout = 2
    
    return managner
}()

// 2. 开始连续定位。调用此方法会cancel掉所有的单次定位请求。
locationManager.startUpdatingLocation()

// 3. 停止连续定位。调用此方法会cancel掉所有的单次定位请求,可以用来取消单次定位。
locationManager.stopUpdatingLocation()

// 4. 单次定位。如果当前正在连续定位,调用此方法将会失败,返回NO。\n该方法将会根据设定的 desiredAccuracy 去获取定位信息。如果获取的定位信息精确度低于 desiredAccuracy ,将会持续的等待定位信息,直到超时后通过completionBlock返回精度最高的定位信息。\n可以通过 stopUpdatingLocation 方法去取消正在进行的单次定位请求。
// withReGeocode: 是否带有逆地理信息(获取逆地理信息需要联网)
locationManager.requestLocation(withReGeocode: true) { (location, regeocode, error) in
    guard error == nil else { return }
    guard let regeocode = regeocode else { return }
    print(regeocode)
}

// 5. 代理回调  AMapLocationManagerDelegate

// 定位成功
func amapLocationManager(_ manager: AMapLocationManager!, didUpdate location: CLLocation!, reGeocode: AMapLocationReGeocode!) {
        
}

// 定位失败
func amapLocationManager(_ manager: AMapLocationManager!, didFailWithError error: Error!) {
    
}

// 请求定位授权
func amapLocationManager(_ manager: AMapLocationManager!, doRequireLocationAuth locationManager: CLLocationManager!) {
    locationManager.requestAlwaysAuthorization()
}

定位 - Google 定位

  1. https://cloud.google.com/maps-platform/ 平台注册开发者账号
  2. 申请 API key
  3. image-20201114173121575
// 设置 API key
GMSServices.provideAPIKey("\(Google API key)")

POI(Point of Interest,兴趣点)&& 地理编码、逆编码 && 路径规划

POI(Point of Interest,兴趣点)&& 地理编码、逆编码 && 路径规划 - 高德地图

高德地图 SDK 封装了 AMapSearchObject 基类对象,具体的搜搜请求对象和响应对象都由子类去继承实现。

// 1、初始化搜索对象,设置代理
private lazy var search: AMapSearchAPI = {
     let object = AMapSearchAPI()!
     object.delegate = self
     return object
}()


//---------  附近 :构造请求对象 ----------- 
let request = AMapPOIAroundSearchRequest()
// 当地定位经纬度
request.location = AMapGeoPoint.location(withLatitude: currentCoordinate!.latitude.cgFloat, longitude: currentCoordinate!.longitude.cgFloat)
request.sortrule = 0
// 类型,多个类型用“|”分割 可选值:文本分类、分类代码 
// 高德地图的POI类别共20个大类,分别为:汽车服务、汽车销售、汽车维修、摩托车服务、餐饮服务、购物服务、生活服务、体育休闲服务、医疗保健服务、住宿服务、风景名胜、商务住宅、政府机构及社会团体、科教文化服务、交通设施服务、金融保险服务、公司企业、道路附属设施、地名地址信息、公共设施,同时,每个大类别都还有二级以及三级的细小划。
request.types = "风景名胜|商务住宅|政府机构及社会团体|交通设施服务|公司企业|道路附属设施|地名地址信息"
request.requireExtension = true
// 当前页数
request.page = page
request.requireSubPOIs = true

// 发起搜索
search.aMapPOIAroundSearch(request)


// ----------------- 关键词搜索 构造请求对象 ------------------------------
let request = AMapPOIKeywordsSearchRequest()
request.keywords = keywords
// 指定城市
request.city = city
request.sortrule = 0
request.requireExtension = true
request.page = page
request.cityLimit = false
request.requireSubPOIs = true
request.types = "风景名胜|商务住宅|政府机构及社会团体|交通设施服务|公司企业|道路附属设施|地名地址信息"

// 关键词搜索 - 高德: 发起关键词搜索
search.aMapPOIKeywordsSearch(request)


// ----------- 地理编码:地理编码,又称为地址匹配,是从已知的地址描述到对应的经纬度坐标的转换过程。该功能适用于根据用户输入的地址确认用户具体位置的场景,常用于配送人员根据用户输入的具体地址找地点。-----------
let request = AMapGeocodeSearchRequest()
// 位置信息:地理编码是依据当前输入,根据标准化的地址结构(省/市/区或县/乡/村或社区/商圈/街道/门牌号/POI)进行各个地址级别的匹配,以确认输入地址对应的地理坐标,只有返回的地理坐标匹配的级别为POI,才会对应一个具体的地物(POI)。
request.address = address

// 发起地理编码搜索请求
search.aMapGeocodeSearch(request)


// -------------------------------- AMapSearchDelegate --------------------------------------
// POI 搜索回调
func onPOISearchDone(_ request: AMapPOISearchBaseRequest!, response: AMapPOISearchResponse!) {
    
    if response.count == 0 {
        return
    }
    
    //解析response获取POI信息
}

// 地理编码(地址转坐标)回调
func onGeocodeSearchDone(_ request: AMapGeocodeSearchRequest!, response: AMapGeocodeSearchResponse!) {
    // 解析response获取地理信息,
   
}

// 路线规划
func onRouteSearchDone(_ request: AMapRouteSearchBaseRequest!, response: AMapRouteSearchResponse!) {
    if response.count > 0 {
        //解析response获取路径信息
    }
}

// 搜索出现错误    
func aMapSearchRequest(_ request: Any!, didFailWithError error: Error!) {

}

POI(Point of Interest,兴趣点)&& 地理编码、逆编码 && 路径规划 - Google 地图

Google 地图这些 POI 搜索,导航数据这些,提供的是 HTTP API 请求调用的方式。

// 谷歌地图 POI 关键词模糊搜索
// https://maps.googleapis.com/maps/api/place/textsearch/json
var params: [String: Any] = [:]
params["key"] = "\(Google API key)"
// 语言 : 
params["language"] = "en"
params["query"] = "\(关键词文本)"
params["inputType"] = "textQuery"
//  params["region"] =


// 谷歌地图 POI 附近搜索
// https://maps.googleapis.com/maps/api/place/nearbysearch/json
var params: [String: Any] = [:]
params["key"] = "\(Google API key)"
// 语言 : 
params["language"] = "en"
// 当前位置 字符串: "纬度,经度"
params["location"] = "\(latitude,longitude)"
// 搜索半径 (单位:米)
params["radius"] = "\(1500)"


// 谷歌地理编码
// https://maps.googleapis.com/maps/api/geocode/json
var params: [String: Any] = [:]
params["key"] = "\(Google API key)"
// 地址文本
params["address"] = address


// 谷歌地址逆编码
// https://maps.googleapis.com/maps/api/geocode/json
var params: [String: Any] = [:]
params["key"] = "\(Google API key)"
params["latlng"] = location.googleDisplay

return .requestParameters(parameters: params,
                          encoding: URLEncoding.default)


// 谷歌路线导航,默认驾车
// https://maps.googleapis.com/maps/api/directions/json
var params: [String: Any] = [:]
params["key"] = "\(Google API key)"
// 出发点坐标位置
params["origin"] = "\(latitude,longitude)"
// 目的地坐标位置
params["destination"] = "\(latitude,longitude)"


// 解析相应数据,模型转换框架使用的是 ObjectMapper 
/// 列表容器模型
struct ListContainerModel<T: Mappable>: Mappable {
    var list: [T]?
    var numberOfElements: Int?
    var total: Int?
    var pageNumber: Int?
    var size: Int?
    var hasNextPage: Bool?
    var hasPreviousPage: Bool?

    init?(map: Map) {
      
    }

    mutating func mapping(map: Map) {
        list <- map["list"]
        numberOfElements <- map["numberOfElements"]
        total <- map["total"]
        pageNumber <- map["pageNumber"]
        size <- map["size"]
        hasNextPage <- map["hasNextPage"]
        hasPreviousPage <- map["hasPreviousPage"]
    }
}

/// google POI 附近和关键词地址搜索
struct GoogleSearchAddressContainerModel: Mappable {
    var html_attributions: [Any]?
    
    /// POI 列表
    var results: [SearchAddressModel]?
    
    /// 返回结果状态
    var status: String?
    
    init?(map: Map) {
      
    }

    mutating func mapping(map: Map) {
        html_attributions <- map["html_attributions"]
        results <- map["results"]
        status <- map["status"]
    }
}

/// leg Model
struct GoogleDirectionLegModel: Mappable  {
    var distance: Any?
    var duration: Any?
    var end_address: String?
    var end_location: GeometryLocation?
    var start_address: String?
    var start_location: GeometryLocation?
    
    var steps: [GoogleDirectionAddressModel]?
    
    init?(map: Map) {
        
    }
    
    mutating func mapping(map: Map) {
        steps <- map["steps"]
    }
}

/// 谷歌地图返回结果模型 
struct GoogleDirectionResultModel: Mappable {
    var steps: [GoogleDirectionAddressModel]? {
        get {
            var result = [GoogleDirectionAddressModel]()
            legs?.forEach({ (leg) in
                result.append(contentsOf: leg.steps ?? [])
            })
            
            return result
        }
    }
    
    var legs: [GoogleDirectionLegModel]?
    
    var points: String?
    
    init?(map: Map) {
        
    }
    
    mutating func mapping(map: Map) {
        legs <- map["routes.0.legs"]
        points <- map["routes.0.overview_polyline.points"]
    }
}

标记点(大头针 annotation || Marker)

标记点(大头针 annotation || Marker)- 高德

// 1. 创建地图对象
let gaodeMap = MAMapView()
gaodeMap.delegate = self
// 缩放级别
zoomLevel = 17
// 是否显示用户位置
isShowsUserLocation = true

// 自定义地图样式(1. 在高德地图开发者应用后台新建地图样式,下载样式的资源包拖到工程里面去)
guard let url = R.file.styleData(),
    let data = try? Data(contentsOf: url) else { return }

let options = MAMapCustomStyleOptions()
options.styleData = data
setCustomMapStyleOptions(options)
customMapStyleEnabled = true

// 设定当前地图的经纬度范围,该范围可能会被调整为适合地图窗口显示的范围
setRegion(MACoordinateRegion(center:  centerCoordinate, span: MACoordinateSpan(latitudeDelta: 0.03, longitudeDelta: 0.03)), animated: false)


// 2. 新建标注
let pointAnnotation = MAPointAnnotation()
// 标注中心点经纬度坐标
pointAnnotation.coordinate = center
// 标注标题
pointAnnotation.title = title
// 标注副标题
pointAnnotation.subtitle = subTitle

// 添加标注 
gaodeMap.addAnnotation(pointAnnotation
// 选中标注数据对应的view。注意:如果annotation对应的annotationView因不在屏幕范围内而被移入复用池,为了完成选中操作,会将对应的annotationView添加到地图上,并将地图中心点移至annotation.coordinate的位置。
gaodeMap.selectAnnotation(pointAnnotation, animated: false)
                       
                       
// 设置标注 的外观样式
// MAMapViewDelegate       
 func mapView(_ mapView: MAMapView!, viewFor annotation: MAAnnotation!) -> MAAnnotationView! {
    if annotation.isKind(of: MAPointAnnotation.self) {
        let pointReuseIndetifier = "pointReuseIndetifier"
        var annotationView: MAPinAnnotationView? = mapView.dequeueReusableAnnotationView(withIdentifier: pointReuseIndetifier) as! MAPinAnnotationView?

        if annotationView == nil {
            annotationView = MAPinAnnotationView(annotation: annotation, reuseIdentifier: pointReuseIndetifier)
        }
        
        // 是否可以点击标注view
        annotationView!.canShowCallout = true
        // 是否有标注添加到地图上的动画效果(从上而下)
        annotationView!.animatesDrop = true
        // 是否可以拖拽
        annotationView!.isDraggable = true
        // 是否有右边的视图
        annotationView!.rightCalloutAccessoryView = nil

        return annotationView!
    }

    return nil
}
                       
// 移除标注
gaodeMap.removeAnnotation(pointAnnotation)             

标记点(大头针 annotation || Marker)- Google

// 创建 Google map
let googleMap = GMSMapView()


// 添加标注 Marker
// 创建 GMSMarker 对象
let marker = GMSMarker()
// 标注位置
marker.position = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
// 标注标题
marker.title = title
// 标注副标题
marker.snippet = subTitle

// 设置标注的图片
marker.icon = UIImage()

// 设置
let camera = GMSCameraPosition.camera(withLatitude: marker.position.latitude, longitude: marker.position.longitude, zoom: 16.0)
marker.map = googleMap

// 自动显示标题内容
selectedMarker = marker


// 移除标注
marker.map = nil

Overlay && Polyline

Overlay && Polyline - 高德

// 多线段的坐标点数组
var lineCoordinates = [CLLocationCoordinate2D]()
// 多边形
let pol = MAPolygon(coordinates: &lineCoordinates, count: UInt(lineCoordinates.count))
gaodeMap.add(pol)

// 不是多边形
let polyline: MAPolyline = MAPolyline(coordinates: &lineCoordinates, count: UInt(lineCoordinates.count))
gaodeMap.addOverlays([polyline])
// 展示路线
showOverlays([polyline], animated: false)

// 移除
removeOverlays(polyline)

Overlay && Polyline - Google

// 多线段的坐标点数组
var lineCoordinates = [CLLocationCoordinate2D]()

// 多边形路径
let gmsPath = GMSMutablePath()
lineCoordinates.forEach { (location) in
    if let latitude = location.latitude, let longitude = location.longitude {
        gmsPath.addLatitude(latitude, longitude: longitude)
    }
}

let polyline = GMSPolyline(path: gmsPath)
// 设置线段颜色
polyline.strokeColor = UIColor._themeColor()
// 设置线段宽度
polyline.strokeWidth = 8.0
// 显示出来
polyline.map = googleMap


// 移除
polyline.map = nil

地理围栏

  • POI 围栏

  • 行政区划 围栏

  • 自定义圆形围栏

  • 自定义多边围栏

// 创建围栏对象
lazy var geoFenceManager: AMapGeoFenceManager = {
    let manager = AMapGeoFenceManager()
    //进入,离开,停留都要进行通知
    manager.activeAction = [AMapGeoFenceActiveAction.inside , AMapGeoFenceActiveAction.outside , AMapGeoFenceActiveAction.stayed]
    manager.allowsBackgroundLocationUpdates = false  // 允许后台定位
    manager.delegate = self

    return manager
}()
    
// 创建行政区域地理围栏
geoFenceManager.addDistrictRegionForMonitoring(withDistrictName: "深圳市", customID: "city")

// 添加圆形围栏
// withCenter: 围栏中心点
//  radius: 围栏半径
geoFenceManager.addCircleRegionForMonitoring(withCenter: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), radius: 500, customID: "123456")

// 移除围栏


// 设置圆形围栏样式
// 渲染多边型 --- MAMapViewDelegate
func mapView(_ mapView: MAMapView!, rendererFor overlay: MAOverlay!) -> MAOverlayRenderer! {
    if overlay.isKind(of: MACircle.self) {
        let renderer: MACircleRenderer = MACircleRenderer(overlay: overlay)
     
        renderer.lineWidth = 1.0
        renderer.strokeColor = UIColor.gxd_themeColor()
        renderer.fillColor = UIColor.gxd_themeColor().withAlphaComponent(0.4)

        return renderer
    }

    return nil
}


// MARK: - AMapGeoFenceManagerDelegate 
// 围栏创建完成
func amapGeoFenceManager(_ manager: AMapGeoFenceManager!, didAddRegionForMonitoringFinished regions: [AMapGeoFenceRegion]!, customID: String!, error: Error!) {
    if let error = error {
        let error = error as NSError
        NSLog("创建失败 %@",error);
    }
    else {
        NSLog("创建成功")
        let circleRegion = regions.first as? AMapGeoFenceCircleRegion

        // 在地图上画一个区域
        guard let center = circleRegion?.center,
            let radius = circleRegion?.radius else { return }
        
        let circle: MACircle = MACircle(center: CLLocationCoordinate2D(latitude: center.latitude, longitude: center.longitude), radius: CLLocationDistance(radius))
        gaodeMap.add(circle)
    }
}

// 围栏状态改变时 用户未知、进入围栏、退出围栏、在围栏内停留
func amapGeoFenceManager(_ manager: AMapGeoFenceManager!, didGeoFencesStatusChangedFor region: AMapGeoFenceRegion!, customID: String!, error: Error!) {
    if error == nil {
        print("status changed \(region.description)")
    } else {
        print("status changed error \(error)")
    }
}

导航

跳转到第三方地图 App 发起导航,先要在 InfoPlist 文件里面配置 QueriesSchemes 字段

<key>LSApplicationQueriesSchemes</key>
<array>
    <string>comgooglemaps</string>
    <string>iosamap</string>
    <string>baidumap</string>
</array>

发起导航:

/// 地图导航模式
enum MapDirectionMode {
    // 驾车
    case driving
    // 步行
    case walking
    // 骑行
    case bicycling
    // 公共交通
    case transit
}

struct DirectionParams {
    /// 源 App 名称
    var sourceApplication: String = U""
    /// 源 App scheme url
    var backScheme: String = "\(appUrlScheme)"

    /// 起始地点
    var origin: Location?
    /// 目的地点
    var destination: Location
    /// 导航模式
    var mode: MapDirectionMode = .driving
}



/// 打开苹果地图导航
/// - Parameter params: 参数
static func openAppleMapDirection(withParams params: DirectionParams) {
    guard let latitude = params.destination.latitude,
        let longitude = params.destination.longitude else { return }

    let currentLocation = MKMapItem.forCurrentLocation()
    let toPlacemark = MKPlacemark(coordinate: CLLocationCoordinate2D(latitude: latitude, longitude: longitude))
    let toLocation = MKMapItem(placemark: toPlacemark)
    toLocation.name = params.destination.name
    MKMapItem.openMaps(with: [currentLocation, toLocation], launchOptions: [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving])
}


/// 打开 google 地图导航
/// - Parameter params: 参数
static func openGoogleMapDirection(withParams params: DirectionParams) {
    if !Keys.MapSchemeUrl.google.isInstalled { return }

    guard var daddr = params.destination.name else {
        return
    }
    daddr = daddr.replacingOccurrences(of: " ", with: "+", options: .literal, range: nil)

    let urlString = Keys.MapSchemeUrl.google.rawValue + "?" +  "x-source=\(params.sourceApplication)&x-success=\(params.backScheme)&saddr=&daddr=\(daddr)&directionsmode=driving"

    guard let url = URL(string: urlString) else { return }
    UIApplication.shared.open(url, options: [:], completionHandler: nil)

}


/// 打开 百度 地图导航
/// - Parameter params: 参数
static func openBaiduMapDirection(withParams params: DirectionParams) {
    if !Keys.MapSchemeUrl.baidu.isInstalled { return }

    guard let destinationLatitude = params.destination.latitude,
                let destinationLongitude = params.destination.longitude else { return }
    let destinationName = params.destination.name ?? ""

    let urlString = Keys.MapSchemeUrl.baidu.rawValue + "map/direction?origin=name:{{我的位置}}&destination=name:\(destinationName)|latlng:\(destinationLatitude),\(destinationLongitude)&mode=driving&coord_type=bd09ll&src=ios.Express.\(params.sourceApplication)"

    guard let url = URL(string: urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "") else { return }
    UIApplication.shared.open(url, options: [:], completionHandler: nil)
}


/// 打开 高德 地图导航
/// - Parameter params: 参数
static func openGaodeMapDirection(withParams params: DirectionParams) {
    if !Keys.MapSchemeUrl.gaode.isInstalled { return }

    guard let latitude = params.destination.latitude,
        let longitude = params.destination.longitude else { return }
    let destinationName = params.destination.name ?? ""
    let urlString = Keys.MapSchemeUrl.gaode.rawValue +  "path?sourceApplication=\(params.sourceApplication)&backScheme=\(params.backScheme)&dlat=\(latitude)&dlon=\(longitude)&dname=\(destinationName)&dev=0&t=0"

    guard let url = URL(string: urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "") else { return }
    UIApplication.shared.open(url, options: [:], completionHandler: nil)
}

参考链接

  1. https://lbs.amap.com/api
  2. https://developers.google.com/places/ios-sdk/overview
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容