openlayer 軌跡線路

一、想要的效果

1、初始效果


1675394076042.png

2、運動時效果


1675394662914.png

二、準備線路坐標點

可以使用地圖拾取器系統

coordinates: [
            [113.53641592772293, 34.82798925347799],
            [113.55119787497061, 34.82789388575531],
            [113.55920874272088, 34.825128232275],
            [113.56130682738113, 34.821408898948555],
            [113.5621651342659, 34.818071041751516],
            [113.56197440405923, 34.808915766567786],
            [113.59115683814126, 34.80662694646218],
            [113.60660636206704, 34.81158605756411]
          ]

繪制軌跡

this.line = new LineString(this.options.coordinates) //  直線
    // 線路所有坐標點坐標
    let coordinates = this.options.coordinates
    this.lineFeature = new ol.Feature({ //  直線線路
      type: 'line',
      geometry: this.line
    })
    this.startMarker = new ol.Feature({ //  開始點位
      type: 'icon',
      geometry: new ol.Point(this.line.getFirstCoordinate())
    })
    this.endMarker = new ol.Feature({ //  結束點位
      type: 'icon',
      geometry: new ol.Point(this.line.getLastCoordinate())
    })
    /**
     * 經停點
     */

    for (let i = 0; i < coordinates.length; i++) {
      let s = new ol.Feature({
        type: 'stop',
        geometry: new ol.Point(coordinates[i])
      })
      this.stopMakers.push(s)
    }

    this.movePoint = this.startMarker.getGeometry().clone() // 移動點
    this.movePointMarker = new ol.Feature({ //  移動點feature
      type: 'geoMarker',
      geometry: this.movePoint
    })

    let routeFeature = [...[this.lineFeature, this.movePointMarker, this.startMarker, this.endMarker], ...this.stopMakers]

    this.trajectoryLayer = new ol.VectorLayer({ // 添加圖層
      source: new ol.VectorSource({
        features: routeFeature,
        className: 'TrajectoryLayer',
        properties: this.options.layerProperties
      }),
      style: (feature) => {
        return this.routeStyle[feature.get('type')]
      }

    })

    this.map.addLayer(this.trajectoryLayer)

三、設置線條、經停點、導航樣式

let coordinates = this.options.coordinates
    let sourcePoint = coordinates[this.currentIndex]
    let targetPoint = coordinates[this.currentIndex + 1]

    this.routeStyle = { // 定義線路樣式
      line: new ol.Style({
        stroke: new ol.Stroke({
          width: this.options.lineStyle.width,
          color: this.options.lineStyle.color
        })
      }),

      geoMarker: new ol.Style({
        image: new ol.Icon({
          src: this.options.navStyle.url,
          scale: this.options.navStyle.scale,
          rotateWithView: false,
          rotation: this.getRotation(sourcePoint, targetPoint)

        })

      }),
      stop: new ol.Style({
        image: new ol.CircleStyle({
          radius: this.options.stopStyle.radius - 2,
          fill: new ol.Fill({
            // width: this.options.routeStyle.width,
            color: this.options.stopStyle.color
          }),
          stroke: new ol.Stroke({
            width: (this.options.stopStyle.radius - 2) / 2,
            color: this.options.stopStyle.color
          })
        })
      })
    }

四、引入 動畫開關、進度條、速度 控制器

this.playbarControl = new PlaybarControl(this.map, _config, (e, pos) => { //  添加播放停止動畫控制器
        if (pos === 100) { // 在尾端
          this.movePoint.setCoordinates(this.options.coordinates[0]) // 設置移動點當前位置
          this.movePointMarker.setGeometry(this.movePoint)
          this.distance = 0
          this.currentIndex = 0
          // this.redrawMovePoing(this.options.coordinates[0], this.options.coordinates) // 設置移動點到一個點位
        }
        this.setAnimation(e)
      }, (e) => {
        this.setAnimationSpeed(e)
      })
      this.map.addControl(this.playbarControl)

源碼

1、 目錄結構


1675394327774.png

2、config.js 默認配置項源碼


import IconNav from '@/assets/images/map/marker-icon/icon-nav.png'

/**
 * 軌跡默認配置項
 * config:{
*     type: 'TrajectoryLayer',
      layerProperties:{ // 圖層屬性配置
        name:'TrajectoryLayer',
        id:'TrajectoryLayer'
      }||undefined // layer 屬性
      coordinates: [], // 道路點位坐標
      layerStyle: [],// 定位點樣式
      action: [{ // marks 事件數組,目前僅支持click 模式
        type: 'click',
        hitTolerance: 10, // 命中容差
        handle: this.handleMarkHover //  事件回調函數
      }]
 */
export const defaultConfig = {
  type: 'TrajectoryLayer',
  layerProperties: {
    name: 'TrajectoryLayer',
    id: 'TrajectoryLayer'
  },
  coordinates: [], // 道路點位坐標
  layerStyle: [], // 樣式配置
  actions: [], //  事件配置
  lineStyle: { // 道路樣式
    width: 5,
    color: '#FF0C00'
  },
  navStyle: { // 導航圖片樣式
    scale: 1,
    url: IconNav
  },
  stopStyle: { // 經停點樣式
    radius: 8,
    color: '#ff8f00'
  },
  speed: 80,
  tolerance: 10, // 容差,最小10
  imageAngle: 1.5 //  圖片自身旋轉角度

}

2、自定義控制器源碼

import { Control } from 'ol/control'
/**
 * 播放控件
 */
export class PlaybarControl extends Control {
  /**
   * @param {Object} [opt_options] Control options.
   */
  constructor (_map, _options, _playCallBack, _speedSetCallBack) {
    const options = _options //  配置項

    let playContainer = document.createElement('div')
    playContainer.id = 'ol-play-control'
    playContainer.className = 'ol-play-control'

    super({
      element: playContainer,
      target: options.target
    })
    this.currentPos = 0
    this.palyStatus = false // 默認暫停
    this.currentSpeed = 1
    this.progressEle = null //  進度條
    this.progressTipContianerEle = null //  提示框
    this.playOptEle = null //  暫停、開始按鈕
    this.initPlay(playContainer)
    this.playCallBack = _playCallBack
    this.speedSetCallBack = _speedSetCallBack

    // this.actionOptions = actionOption
  }

  setProgressEleWidth (pos) {
    if (this.progressEle && this.progressTipContianerEle) {
      this.currentPos = pos

      if (!this.palyStatus) {
        this.palyStatus = true
        this.playOptEle.className = `${this.palyStatus ? 'ol-opt-playing ol-play-opt' : 'ol-play-opt'}`
        // console.log('狀態是false 時,this.palyStatus', this.palyStatus, pos, Math.round(pos))
      }

      // console.log('this.palyStatus', this.palyStatus)
      if (this.currentPos > 0 && Math.round(pos) < 100) {
        this.progressEle.style.width = this.currentPos + '%'
      } else if (this.currentPos === 0 || this.currentPos < 0) {
        this.progressEle.style.width = '0px'
      } else if (Math.round(pos) === 100 || Math.round(pos) > 100) {
        this.currentPos = 100
        this.progressEle.style.width = '100%'
        this.palyStatus = false
        this.playOptEle.className = `${this.palyStatus ? 'ol-opt-playing ol-play-opt' : 'ol-play-opt'}`
        // console.log('進入', this.palyStatus)
      }

      if (this.currentPos === 0 || this.currentPos < 0) {
        this.progressTipContianerEle.style.display = 'none'
      } else {
        this.progressTipContianerEle.style.display = 'block'
        let width = this.progressTipContianerEle.clientWidth
        // console.log('width=', width)
        this.progressTipContianerEle.style.left = width / 2 + 'px'
        // if (this.currentPos > 0 && this.currentPos < 100) {
        //   this.progressTipContianerEle.style.left = width / 2 + 'px'
        // } else if (this.currentPos === 0 || this.currentPos < 0) {
        //   this.progressTipContianerEle.style.left = '100px'
        // } else if (Math.round(pos) === 100 || Math.round(pos) > 100) {
        //   this.currentPos = 100
        //   this.progressEle.stylestyle.left = width + 'px'
        //   this.palyStatus = false
        // }
      }
    }
  }

  /**
   * 播放
   */
  initPlay (playContainer) {
    this.initPlayOptsContainer(playContainer)
    this.initProgressContainer(playContainer)
    this.initSpeedContainer(playContainer)
  }

  /**
   * 初始化播放、暫停box
   */
  initPlayOptsContainer (playContainer) {
    this.playOptEle = document.createElement('div') //  開啟、停止按鈕
    this.playOptEle.className = `${this.palyStatus ? 'ol-opt-playing ol-play-opt' : 'ol-play-opt'}`
    playContainer.appendChild(this.playOptEle)
    this.playOptEle.addEventListener('click', (e) => {
      // console.log('ss')
      this.palyStatus = !this.palyStatus
      this.playOptEle.className = `${this.palyStatus ? 'ol-opt-playing ol-play-opt' : 'ol-play-opt'}`
      // console.log('this.onPlay', this.playCall)
      this.playCallBack(this.palyStatus, this.currentPos)
    })
  }

  /**
   * 初始化進度條容器box
   * @param {*} playContainer
   */
  initProgressContainer (playContainer) {
    let progressContainerEle = document.createElement('div') //  進度條box容器
    progressContainerEle.className = 'ol-progress-container'
    this.progressEle = document.createElement('div') //  進度條容器
    this.progressEle.className = 'ol-progress'
    progressContainerEle.appendChild(this.progressEle) // 將進度條容器 寫入 進度條box容器

    this.progressTipContianerEle = document.createElement('div') //  進度提示容器
    this.progressTipContianerEle.className = 'ol-progress-tip-container'
    this.progressTipContianerEle.innerText = '當前速度是'
    let speedTipEle = document.createElement('div') //  速度容器
    speedTipEle.className = 'ol-progress-tip-speed'
    speedTipEle.innerText = '0km/h'

    this.progressEle.appendChild(this.progressTipContianerEle) // 將速度文字提示ele 寫入 進度條

    this.progressTipContianerEle.appendChild(speedTipEle) // 將速度容器ele 寫入 進度條
    playContainer.appendChild(progressContainerEle) // 進度條box容器 play控制器

    // setTimeout(() => {
    //   let width = this.progressTipContianerEle.clientWidth
    //   this.progressTipContianerEle.style.left = width / 2 + 'px'
    //   console.log('width', width)
    // }, 100)
  }

  /**
   * 初始化速度設置容器box
   * @param {*} playContainer
   */
  initSpeedContainer (playContainer) {
    let playSpeed = document.createElement('select') //  播放速度樣式
    playSpeed.className = 'ol-speed'
    playSpeed.options.add(new Option('1x', 1))// 兼容所有瀏覽器
    playSpeed.options.add(new Option('1.5x', 1.5))// 兼容所有瀏覽器
    playSpeed.options.add(new Option('2x', 2))// 兼容所有瀏覽器
    playContainer.appendChild(playSpeed)
    playSpeed.addEventListener('change', (e) => {
      // console.log('選擇 e', e, e.target.value)
      this.speedSetCallBack(e.target.value)
    })
  }
}

3、全局添加控制器樣式

#ol-play-control{
  padding:10px 15px;
  background: rgba(0,0,0,.5);
  color:#fff;
  position: absolute;
  bottom: 10px;
  right:10px;
  border-radius: 10px;
  left: 10px;
  @include flexLayout($vertical:center);
  font-size: 12px;
  .ol-play-opt{
    position: relative;
    width: 30px;
    height: 30px;
    cursor: pointer;
    &::before{
      content:'';
      position: absolute;
      top:5px;
      left:9px;
      border-style: solid;
      border-color: transparent;
      border-width: 12px 0 12px 12px;
      border-left-color: #fff;
    }
    &.ol-opt-playing{
      &::before,&::after{
        content:'';
        position: absolute;
        width:4px;
        // height: 10px;
        top:5px;
        bottom:5px;
        background: #fff;
        border-radius: 5px;
      }
      &::before{
        left:8px;
        border:0
      }
      &::after{
        right:8px
      }
    }
  }
  .ol-progress-container{
    flex:1;
    margin:0 5px;
    background: rgba(#fff,.5);
    height: 5px;
    position: relative;
    
    .ol-progress{
      height: 100%;
      background: #fff;
      width: 0%;
      position: relative;
      @include flexLayout($vertical:center,$horizontal:flex-end);
      .ol-progress-tip-container{
       display: none;
        top:-30px;
        left:65px;
        width:auto;
        min-width: 130px;
        position: relative;
        // background: #fff;
        padding:10px;
        // padding-left: 0;
        background-color: #fff;
        // border-radius: 5px;
        color:#000;
        font-weight: bold;
        border-radius: 8px;
        border-radius: 8px;
        box-shadow: 0 0 5px rgba(#000,.4);
        .ol-progress-tip-speed{
          display: inline-block;
          color:#4969DC;
          margin-left:10px;
        }
        &::after{
          content:"";
          position: absolute;
          bottom:-22px;
          left:50%;
          margin-left: -12px;
          border-style: solid;
          border-color: transparent;
          border-width: 12px;
          border-top-color: #fff;
        }
      }
     
    }
   
  }
  .ol-speed{
    border:1px solid rgba(#fff,.8);
    background-color: transparent;
    color:#fff;
    cursor: pointer;
    border-radius: 5px;
    margin-left: 5px;
    text-align: center;
    padding:2px 0px;
    font-size:14px;
    position: relative;
    option{
      color:#000;
      border-color:rgba(#000,.4)
    }
  }
}

4、軌跡類封裝

import * as ol from '../openLayer'
import { getVectorContext } from 'ol/render.js'
import { getDistance } from 'ol/sphere.js'
import { PlaybarControl } from './play'
import { defaultConfig } from './config'
import { deepAssign } from '@/utils'
import { LineString } from 'ol/geom'
/**
 * 軌跡類
 */
export default class OlMapTrajectory {
  constructor (_map, _config) {
    this.map = _map
    this.options = deepAssign(defaultConfig, _config) //  配置項
    this.line = null // 直線
    this.lineFeature = null // 直線feature
    this.startMarker = null // 開始點位
    this.endMarker = null // 結束點位
    this.stopMakers = [] // 經停點
    this.routeStyle = null // 軌跡樣式
    this.movePoint = null // 移動點
    this.movePointMarker = null //  移動點feature
    this.moveFeature = null // 移動feature
    this.lastTime = null //  最后一次移動時間
    this.animating = false // 是否正在移動,false-> 停止 true-> 移動
    this.trajectoryLayer = null //  openlayer 對象
    this.speed = this.options.speed //  移動速度
    this.distance = 0 // 已經移動的距離
    this.currentIndex = 0 //  當前坐標點下標
    this.lineAllLength = 0 //  軌跡總長度
    this.lineSegmentLength = [] // 線段直接的距離
    this.vectorContext = null // canvas 上下文
    this.tolerance = this.options.tolerance < 10 ? 10 : this.options.tolerance // 容差,最小為10
    this.imageAngle = this.options.imageAngle //  圖片自身旋轉角度
    if (this.options.coordinates.length > 0) {
      this.setRouteStyle() //  設置軌跡樣式
      this.drwaRoute() //  繪制線條
      this.initMoveFeature() //  初始化移動moveFeature
      this.lineAllLength = this.getLineAllLength() //  獲取線條總長度
      this.playbarControl = new PlaybarControl(this.map, _config, (e, pos) => { //  添加播放停止動畫控制器
        if (pos === 100) { // 在尾端
          this.movePoint.setCoordinates(this.options.coordinates[0]) // 設置移動點當前位置
          this.movePointMarker.setGeometry(this.movePoint)
          this.distance = 0
          this.currentIndex = 0
          // this.redrawMovePoing(this.options.coordinates[0], this.options.coordinates) // 設置移動點到一個點位
        }
        this.setAnimation(e)
      }, (e) => {
        this.setAnimationSpeed(e)
      })
      this.map.addControl(this.playbarControl)
    }
  }

  /**
   * 重新繪制移動點位置和樣式
   */
  redrawMovePoing (currentCoordinate, coordinates, vectorContext) {
    this.movePoint.setCoordinates(currentCoordinate) // 設置移動點當前位置
    // console.log('this.currentIndex=', this.currentIndex, this.getRotation(coordinates[this.currentIndex], coordinates[this.currentIndex + 1]))
    let rotation = this.getRotation(coordinates[this.currentIndex], coordinates[this.currentIndex + 1])
    if (rotation > 3) {
      rotation = rotation - this.imageAngle
    }
    let movePointStyle = new ol.Style({
      image: new ol.Icon({
        src: this.options.navStyle.url,
        scale: this.options.navStyle.scale,
        rotateWithView: false,
        rotation: rotation

      })
    })
    this.vectorContext.setStyle(movePointStyle) //  移動時重新計算線路角度
    this.vectorContext.drawGeometry(this.movePoint)
  }

  /**
 * 得到總線路長度
 * @returns
 */
  getLineAllLength () {
    let coordinates = this.options.coordinates
    let length = 0
    for (let i = 0; i < coordinates.length - 1; i++) {
      // console.log(coordinates[i], i)
      length = length + this.formatLength(coordinates[i], coordinates[i + 1])
      this.lineSegmentLength.push(parseInt(length))
    }
    console.log('this.lineSegmentLength', this.lineSegmentLength)
    return length
  }

  /**
   * 設置動畫的速度
   * @param {*} e
   */
  setAnimationSpeed (e) {
    this.speed = e * this.options.speed
    // this.initMoveFeature()
  }

  /**
   * 開啟或者停止動畫
   * @param {*} e
   */
  setAnimation (e) {
    // console.log('e', e)
    this.animating = e
    if (this.animating) {
      this.startAnimation()
    } else {
      this.stopAnimation()
    }
  }
  /**
   * 開始動畫
   */

  startAnimation () {
    console.log('開始移動')
    this.animating = true
    this.lastTime = new Date().getTime() /** 開始時的時間 */
    this.trajectoryLayer.on('postrender', this.moveFeature)
    // hide geoMarker and trigger map render through change event
    this.movePointMarker.setGeometry(null)
  }

  /**
 * 停止動畫
 */
  stopAnimation () {
    this.animating = false

    // Keep marker at current animation position
    this.movePointMarker.setGeometry(this.movePoint)
    this.trajectoryLayer.un('postrender', this.moveFeature)
  }

  /**
 * 獲取線段長度
 * @param {*} line
 * @returns
 */
  formatLength (sourcePoint, targetPoint) {
    const length = getDistance(sourcePoint, targetPoint)
    let output
    if (length > 1000) {
      output = Math.round((length / 1000) * 100) / 100 + ' km'
    } else {
      output = Math.round(length * 100) / 100 + ' m'
    }
    return getDistance(sourcePoint, targetPoint)
  };
  /**
  * 根據坐標獲取角度數,以正上方為0度作為參照
  * @param  sourcePoint 源點
  * @param  targetPoint 目標點
  */

  getRotation (sourcePoint, targetPoint) {
    try {
      return -Math.atan2(targetPoint[1] - sourcePoint[1], targetPoint[0] - sourcePoint[0]) + this.imageAngle //  計算導航圖標旋轉角度
    } catch (error) {
      console.log(error, sourcePoint, targetPoint)
    }
  }

  /**
   * 初始化移動
   */
  initMoveFeature () {
    if (this.moveFeature) {
      console.log('已存在', this.moveFeature)
    } else {
      let routeLength = this.options.coordinates.length
      let coordinates = this.options.coordinates
      let moveLength = 0
      let preCoordinate = this.options.coordinates[0]

      this.moveFeature = (e) => {
        // console.log('moveFeaturee=', e)
        this.vectorContext = getVectorContext(e) // //HTML5 Canvas context,ol.render.canvas.Immediate的對象
        const speed = parseInt(this.speed)
        let frameState = e.frameState // freme 的狀態
        const time = frameState.time
        const elapsedTime = time - this.lastTime
        if (this.distance === 0) {
          preCoordinate = this.options.coordinates[0]
          moveLength = 0
          this.lastTime = new Date().getTime()
        }
        // console.log('frameStateindex=', frameState.index)
        this.distance = (this.distance + (speed * elapsedTime) / 1e6) % 2

        // console.log('this.currentIndex=', this.currentIndex)
        // console.log('distance', distance)
        this.lastTime = time
        const currentCoordinate = this.line.getCoordinateAt(
          this.distance > 1 ? 2 - this.distance : this.distance
        )
        moveLength = moveLength + this.formatLength(preCoordinate, currentCoordinate) // 計算移動過的距離
        preCoordinate = currentCoordinate //  緩存上一個定位點
        // console.log('moveLength=', currentCoordinate, moveLength)
        // console.log('moveLength=', moveLength, this.lineSegmentLength.indexOf(moveLength))

        let progressWidth = (moveLength / this.lineAllLength) * 100 //  計算計算進度條的位置
        // console.log('progressWidth=', progressWidth)
        this.playbarControl.setProgressEleWidth(progressWidth) //  設置進度條的位置
        this.getCurrentIndex(moveLength, coordinates) // 計算移動點在坐標數組里的下標
        this.redrawMovePoing(currentCoordinate, coordinates) // 從新繪制移動點的位置
        this.map.render() // 繼續動畫效果
        if (this.distance > 1) {
          this.stopAnimation()
        }
      }
    }
  }

  /**
   * 計算當前坐標點下標,
   * moveLength
   */
  getCurrentIndex (moveLength, coordinates) {
    let filtCoordinate = this.lineSegmentLength.filter(item => {
      let tempMoveLength = parseInt(moveLength)
      let tempItem
      // console.log(tempMoveLength, item)
      if (tempMoveLength > item - this.tolerance && tempMoveLength < item + this.tolerance) {
        tempItem = item
      }
      return tempItem
    })
    // console.log('filtCoordinate=', filtCoordinate)
    if (filtCoordinate.length > 0) {
      this.currentIndex = this.lineSegmentLength.indexOf(filtCoordinate[0])
      // console.log('this.currentIndex=', this.currentIndex)
    }
    if (this.currentIndex >= coordinates.length) {
      this.currentIndex = coordinates.length - 1
    }
  }

  /**
   * 道路軌跡
   */
  drwaRoute () {
    this.line = new LineString(this.options.coordinates) //  直線
    // 線路所有坐標點坐標
    let coordinates = this.options.coordinates
    this.lineFeature = new ol.Feature({ //  直線線路
      type: 'line',
      geometry: this.line
    })
    this.startMarker = new ol.Feature({ //  開始點位
      type: 'icon',
      geometry: new ol.Point(this.line.getFirstCoordinate())
    })
    this.endMarker = new ol.Feature({ //  結束點位
      type: 'icon',
      geometry: new ol.Point(this.line.getLastCoordinate())
    })
    /**
     * 經停點
     */

    for (let i = 0; i < coordinates.length; i++) {
      let s = new ol.Feature({
        type: 'stop',
        geometry: new ol.Point(coordinates[i])
      })
      this.stopMakers.push(s)
    }

    this.movePoint = this.startMarker.getGeometry().clone() // 移動點
    this.movePointMarker = new ol.Feature({ //  移動點feature
      type: 'geoMarker',
      geometry: this.movePoint
    })

    let routeFeature = [...[this.lineFeature, this.movePointMarker, this.startMarker, this.endMarker], ...this.stopMakers]

    this.trajectoryLayer = new ol.VectorLayer({ // 添加圖層
      source: new ol.VectorSource({
        features: routeFeature,
        className: 'TrajectoryLayer',
        properties: this.options.layerProperties
      }),
      style: (feature) => {
        return this.routeStyle[feature.get('type')]
      }

    })

    this.map.addLayer(this.trajectoryLayer)
  }

  /**
   * 設置軌跡線路樣式
   */
  setRouteStyle () {
    let coordinates = this.options.coordinates
    let sourcePoint = coordinates[this.currentIndex]
    let targetPoint = coordinates[this.currentIndex + 1]

    this.routeStyle = { // 定義線路樣式
      line: new ol.Style({
        stroke: new ol.Stroke({
          width: this.options.lineStyle.width,
          color: this.options.lineStyle.color
        })
      }),

      geoMarker: new ol.Style({
        image: new ol.Icon({
          src: this.options.navStyle.url,
          scale: this.options.navStyle.scale,
          rotateWithView: false,
          rotation: this.getRotation(sourcePoint, targetPoint)

        })

      }),
      stop: new ol.Style({
        image: new ol.CircleStyle({
          radius: this.options.stopStyle.radius - 2,
          fill: new ol.Fill({
            // width: this.options.routeStyle.width,
            color: this.options.stopStyle.color
          }),
          stroke: new ol.Stroke({
            width: (this.options.stopStyle.radius - 2) / 2,
            color: this.options.stopStyle.color
          })
        })
      })
    }
  }

  updateTrajectory (_map, _config) {
    this.map = _map
    this.options = deepAssign(defaultConfig, _config) //  配置項
  }
}

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,748評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,165評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,595評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,633評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,435評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,943評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,035評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,175評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,713評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,599評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,788評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,303評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,034評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,412評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,664評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,408評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,747評論 2 370

推薦閱讀更多精彩內容