农作物管理系统(openlayers、python、django)

农作物管理系统(openlayers、python、django)

客户需求

项目名称是:农作物长势遥感监测信息发布平台

  1. 农作物长势分析:做成下拉菜单,下拉菜单里面是月份。点击地块之后可以出现每个月份的长势图(是曲线图)
  2. 农作物分类:农作物共分了6类,也可以做成下拉菜单,点击菜单出现棉花,玉米等类,在点击下拉菜单的时候可以在图中显示对应的种类。
  3. 农作物面积:也是下拉菜单,也主要是棉花,玉米,小麦等5类作物面积。在点击下拉菜单时出现地块和面积统计数据。(要是地块数据不好显示可以只做显示面积数据)
  4. 用户在点击每个地块数据时可以给用户显示地块的种植类和面积已经属于那个乡镇
  5. 定位用户的地理位置,可以显示该位置的地块信息(如果是耕地就显示,不是耕地就不用显示了)
  6. 地图常用的放大缩小功能,和搜索查询功能

以上是原始需求,实现上做了一些改动

原始数据

原始数据,点我

软件安装

  1. 用于发布地图服务java环境:Tomcat + geoserver + jdk
  2. 用于后端python
  3. 用于前端nodejs
  4. 开发工具visual studio code、pycharm等

服务发布

简单发布shp 和 tif影像数据wms服务即可
在这里插入图片描述

源码

前端主要是react+dva+umi+antdesign框架

前端 核心源码 pages/openlayers_nongye/index.js

/* global mars3d Cesium*/
import React, { Component } from 'react';
import { connect } from 'dva';
import { Divider, Checkbox, Button, Row, Col, Tooltip, Modal, Select, Tree,Icon } from 'antd'
import styles from './style.less'
import echarts from 'echarts'
import Pie from './pie'
import Source from './source'

const { TreeNode } = Tree;

/* openlayers */
import 'ol/ol.css';
import Map from 'ol/Map';
import View from 'ol/View';
import Feature from 'ol/Feature';
import { OSM, XYZ } from 'ol/source';
import { LineString, Point, Polygon } from 'ol/geom';
import { defaults as defaultInteractions, Pointer as PointerInteraction } from 'ol/interaction';
import { Image as ImageLayer,Tile as TileLayer, Vector as VectorLayer } from 'ol/layer';
import { ImageWMS ,TileJSON, Vector as VectorSource } from 'ol/source';
import { Fill, Stroke, Style } from 'ol/style';
import { toLonLat, fromLonLat, get } from 'ol/proj';
import { FullScreen } from 'ol/control';
import filter,{or,like,and, equalTo} from 'ol/format/filter';
import GeoJSON from 'ol/format/GeoJSON';
import { none } from 'ol/centerconstraint';


const chinaJson = require('./file/wuhan.geojson')
const { Option, OptGroup } = Select;

@connect(({ nongye }) => ({
  nongye
}))

class Nongye extends Component {

  constructor(props) {
    super(props)
    this.state = {
      map: null,
      chart: null,
      //底图
      digitalLayer: new TileLayer({
        //source: new OSM()
        source: new XYZ({
          url: 'https://wprd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}'
        })
      }),
      imagesLayer: new TileLayer({
        //source: new OSM()
        source: new XYZ({
          url: 'https://wprd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=6&x={x}&y={y}&z={z}'
        })
      }),
      arcgisLayer: new TileLayer({
        source: new XYZ({
          //url: 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png'
          url: 'http://cache1.arcgisonline.cn/arcgis/rest/services/ChinaOnlineStreetPurplishBlue/MapServer/tile/{z}/{y}/{x}'
        })
      }),
      //业务图层
      wushuImage:new ImageLayer({
        source: new ImageWMS({
          url: '/nongye/geoserver/nongye/wms',
          params: {'LAYERS': 'nongye:geotiff_coverage'},
          ratio: 1,
          serverType: 'geoserver',
          crossOrigin: 'anonymous',
        }),
      }),
      wushuBoundary:new ImageLayer({
        source: new ImageWMS({
          url: '/nongye/geoserver/nongye/wms',
          params: {'LAYERS': 'nongye:wusubound'},
          ratio: 1,
          serverType: 'geoserver',
          crossOrigin: 'anonymous',
        }),
      }),
      wushuCrops:new ImageLayer({
        source: new ImageWMS({
          url: '/nongye/geoserver/nongye/wms',
          params: {
            'LAYERS': 'nongye:wusugd',
            // 'CQL_FILTER': `kind='mianhua'`
          },
          ratio: 1,
          serverType: 'geoserver',
          crossOrigin: 'anonymous',
        }),
      }),
      wuhanGeojson: new VectorLayer({
        source: new VectorSource({
          url: chinaJson,
          format: new GeoJSON()
        }),
        style: new Style({
          stroke: new Stroke({
            color: '#319FD3',
            width: 20
          })
        })
      }),
      showChart: false,
      crop_prop:null,//点击地块详细信息
    }
  }

  componentDidMount() {
    this.start()
    this.setState({
      chart: this.refs.chart
    })
  }

  start = () => {
    this.initMap({
    }).then(() => {
      const {map,wushuCrops} = this.state
      //监听动作
      let that = this
      map.on('singleclick', function (evt) {
        // document.getElementById('info').innerHTML = '';
        var viewResolution = /** @type {number} */ (map.getView().getResolution());
        var url = wushuCrops.getSource().getFeatureInfoUrl(
          evt.coordinate,
          viewResolution,
          'EPSG:3857',
          {'INFO_FORMAT': 'application/json'}
        );
        if (url) {
          fetch(url)
            .then(function (response) { return response.text(); })
            .then(function (result) {
              console.log(result)
              let json =JSON.parse(result)
              that.setState({
                showChart:false,
                crop_prop:json.features.length>0 ?json.features[0].properties:null
              })
            });
        }
      });
      
      map.on('pointermove', function (evt) {
        if (evt.dragging) {
          return;
        }
        var pixel = map.getEventPixel(evt.originalEvent);
        var hit = map.forEachLayerAtPixel(pixel, function () {
          return true;
        });
        map.getTargetElement().style.cursor = hit ? 'pointer' : '';
      });
    })
  }

  initMap = () => {
    const { wuhanGeojson, arcgisLayer, imagesLayer, digitalLayer,wushuBoundary,wushuImage,wushuCrops } = this.state
    arcgisLayer.set('id', 'arcgisLayer')
    arcgisLayer.setVisible(false)
    imagesLayer.set('id', 'imagesLayer')
    imagesLayer.setVisible(false)
    digitalLayer.set('id', 'digitalLayer')
    digitalLayer.setVisible(true)
    wushuImage.set("id",'wushuImage')
    wushuImage.setVisible(false)
    wushuBoundary.set("id",'wushuBoundary')
    wushuBoundary.setVisible(false)
    wushuCrops.set("id",'wushuCrops')
    wushuCrops.setVisible(true)
    return new Promise(resolve => {
      var fullScreenControl = new FullScreen()
      var map = new Map({
        interactions: defaultInteractions().extend([new Drag()]),
        view: new View({
          center: fromLonLat([84.41,44.60]),
          zoom: 9
        }),
        layers: [
          arcgisLayer,
          imagesLayer,
          digitalLayer,
          wuhanGeojson,
          wushuImage,
          wushuBoundary,
          wushuCrops,
        ],
        target: 'map'
      });
      map.addControl(fullScreenControl)
      this.setState({
        map
      },()=>{
        resolve()
      })
    })
  };

  //定位
  location =()=>{
    const {map} = this.state
    let bmap = new BMap.Map("allmap");

    var geolocation = new BMap.Geolocation();
    geolocation.getCurrentPosition(function(r){
        if(this.getStatus() == BMAP_STATUS_SUCCESS){
            // alert('您的位置:'+r.point.lng+','+r.point.lat);
            map.setView(new View({
              center: fromLonLat([r.point.lng,r.point.lat]),
              zoom: 10
            }))
        }
        else {
            alert('failed'+this.getStatus());
        }
    });
  }
  //影像和电子地图切换
  anhei = () => {
    const { map, arcgisLayer } = this.state
    var layers = map.getLayers()
    for (let i = 0; i < layers.getLength(); i++) {
      var id = layers.item(i).get('id')
      if (id === 'imagesLayer' || id === 'digitalLayer') {
        layers.item(i).setVisible(false)
      }
    }
    arcgisLayer.setVisible(true)
  }
  weixing = () => {
    const { map, imagesLayer } = this.state
    var layers = map.getLayers()
    for (let i = 0; i < layers.getLength(); i++) {
      var id = layers.item(i).get('id')
      if (id === 'arcgisLayer' || id === 'digitalLayer') {
        layers.item(i).setVisible(false)
      }
    }
    imagesLayer.setVisible(true)
  }
  luwang = () => {
    const { map, digitalLayer } = this.state
    var layers = map.getLayers()
    for (let i = 0; i < layers.getLength(); i++) {
      var id = layers.item(i).get('id')
      if (id === 'arcgisLayer' || id === 'imagesLayer') {
        layers.item(i).setVisible(false)
      }
    }
    digitalLayer.setVisible(true)
  }


  onCheck = (checkedKeys, info) => {
    const {wushuCrops,wushuImage,wushuBoundary} = this.state
    //显示隐藏图层数据
    if(checkedKeys.includes("wushu")){//两者皆有
      wushuImage.setVisible(true)
      wushuBoundary.setVisible(true)
    }else if (checkedKeys.includes("image")) { //只有影像
      wushuImage.setVisible(true)
      wushuBoundary.setVisible(false)
    }else if(checkedKeys.includes("boundary")){ //只有边界
      wushuImage.setVisible(false)
      wushuBoundary.setVisible(true)
    }else{
      wushuImage.setVisible(false)
      wushuBoundary.setVisible(false)
    }

    //过滤crops业务图层要素数据
    let p = ''
    checkedKeys.forEach(element => {
      p += "'"+element+"' ,"
    });
    
    wushuCrops.getSource().updateParams({
      'CQL_FILTER': `kind in ${'('+p.substr(0,p.length-1)+')'}`
    })
    wushuCrops.getSource().refresh()
  };

  getArrDifference =(arr1, arr2)=> {
    return arr1.concat(arr2).filter(function(v, i, arr) {
        return arr.indexOf(v) === arr.lastIndexOf(v);
    });
  } 

  tongji =()=>{
    const {crop_prop } = this.state
    this.setState({
      showChart:true
    },()=>{
      getFromCache("tongjiArea").then(data=>{
        if(!data){
          this.props.dispatch({
            type: 'nongye/getTongjiArea',
            payload: {},
          }).then(result => {
            
            setToCache("tongjiArea",result)
            this.initBar(result.data)
          })
        }else{
          this.initBar(data.data)
        }
      })
    })
  }

  initBar = (data)=>{
    var myChart = echarts.init(document.getElementById('chart'));
      var option = {
        title: {
            text: '农作物面积统计(平方米)'
        },
        tooltip: {},
        xAxis: {
          type: 'category',
          data: ['棉花', '番茄', '蔬菜','小麦','玉米','其他']
        },
        yAxis: {
            type: 'value'
        },
        grid: {
          bottom: '10%',
          left: '3%',
          containLabel: true
        },
        series: [{
            data: [data['mianhua'],data['fanqie'],data['shucai'],data['xiaomai'],data['yumi'],data['qita']],
            type: 'bar'
        }]
      };
      myChart.setOption(option);
  }
  
  zhangshi = () =>{
    const {crop_prop } = this.state
    this.setState({
      showChart:true
    },()=>{
      var myChart = echarts.init(document.getElementById('chart'));
      var option = {
        title: {
            text: '长势'
        },
        tooltip: {},
        xAxis: {
          type: 'category',
          data: ['3月', '5月', '6月','7月','8月','9月','10月']
        },
        yAxis: {
            type: 'value'
        },
        grid: {
          bottom: '10%',
          containLabel: true
        },
        series: [{
            data: [crop_prop.b1_all_tif, crop_prop.b2_all_tif,crop_prop.b3_all_tif,crop_prop.b4_all_tif
              ,crop_prop.b5_all_tif,crop_prop.b6_all_tif,crop_prop.b7_all_tif],
            type: 'line'
        }]
      };
      myChart.setOption(option);
    })
    
  }

  NDVI = () =>{
    const {crop_prop } = this.state
    this.setState({
      showChart:true
    },()=>{
      var myChart = echarts.init(document.getElementById('chart'));
      var option = {
        title: {
            text: 'NDVI'
        },
        tooltip: {},
        grid: {
          bottom: '10%',
          containLabel: true
        },
        xAxis: {
          type: 'category',
          data: ['3月12', '4月11', '5月26','6月20','7月10','8月24','9月3','10月18']
        },
        yAxis: {
            type: 'value'
        },
        series: [{
            data: [crop_prop.b1_ndvi202 ,crop_prop.b2_ndvi202	,crop_prop.b3_ndvi202	,crop_prop.b4_ndvi202	
              ,crop_prop.b5_ndvi202	,crop_prop.b6_ndvi202	,crop_prop.b7_ndvi202	,crop_prop.b8_ndvi202],
            type: 'line'
        }]
      };
      myChart.setOption(option);
    })
  }

  render() {
    const { map, chart,crop_prop,showChart } = this.state
    return (
      <div style={{ width: '100%', paddingTop: '80px' }}>
        <div className={styles.layer}>
          <Tree
            checkable
            defaultExpandedKeys={['wushu','crops']}
            defaultSelectedKeys={['crops']}
            defaultCheckedKeys={['crops']}
            // onSelect={this.onSelect}
            onCheck={this.onCheck}
          >
            <TreeNode title="乌苏区位图" key="wushu">
              <TreeNode title="影像" key="image"></TreeNode>
              <TreeNode title="边界" key="boundary"></TreeNode>
            </TreeNode>
            <TreeNode title="全部农作物" key="crops">
              <TreeNode title="番茄" key="fanqie"></TreeNode>
              <TreeNode title="棉花" key="mianhua"></TreeNode>
              <TreeNode title="蔬菜" key="shucai"></TreeNode>
              <TreeNode title="小麦" key="xiaomai"></TreeNode>
              <TreeNode title="玉米" key="yumi"></TreeNode>
              <TreeNode title="其他" key="qita"></TreeNode>
            </TreeNode>
          </Tree>
          <Divider orientation="right"><a onClick={this.tongji}>统计面积</a></Divider>
          <div className={styles.desc}>
            { crop_prop && (
              <>
                {/* <div className={styles.title}>农作物详情</div> */}
                <Divider orientation="left">农作物详情</Divider>
                <div className={styles.item}><label>乡镇:</label><span>{crop_prop.xiangzhen}</span></div>
                <div className={styles.item}><label>土地:</label><span>{crop_prop.classname}</span></div>
                <div className={styles.item}><label>类型:</label><span>{crop_prop.kind}</span></div>
                <div className={styles.item}><label>面积:</label><span>{crop_prop.Shape_Area}</span></div>
                <div className={styles.item}><label>编码:</label><span>{crop_prop.code}</span></div>
                <Divider orientation="right"><a onClick={this.zhangshi}>查看长势</a></Divider>
                <Divider orientation="right"><a onClick={this.NDVI}>查看NDVI</a></Divider>
              </>
              )
            }
          </div>
          {
            showChart && (
              <div className={styles.chart}>
                <div id="chart" ref="chart" style={{ width: '100%',height:'100%' }}></div>
              </div>
            )
          }
        </div>
        <div className={styles.map} id="map">
          <div className={styles.toolbar}>
            <div className={styles.bar} onClick={this.location}>
              <div id="allmap" style={{display:none}}></div>
              <p><Icon type="environment" /></p>
            </div>
            <div className={styles.bar} onClick={this.anhei}>
              <p>暗黑</p>
            </div>
            <div className={styles.bar} onClick={this.luwang}>
              <p>路网</p>
            </div>
            <div className={styles.bar} onClick={this.weixing}>
              <p>卫星</p>
            </div>
          </div>
        </div>
      </div>
    )
  }
}


var Drag = /*@__PURE__*/(function (PointerInteraction) {
  function Drag() {
    PointerInteraction.call(this, {
      handleDownEvent: handleDownEvent,
      handleDragEvent: handleDragEvent,
      handleMoveEvent: handleMoveEvent,
      handleUpEvent: handleUpEvent
    });

    /**
     * @type {import("../src/ol/coordinate.js").Coordinate}
     * @private
     */
    this.coordinate_ = null;

    /**
     * @type {string|undefined}
     * @private
     */
    this.cursor_ = 'pointer';

    /**
     * @type {Feature}
     * @private
     */
    this.feature_ = null;

    /**
     * @type {string|undefined}
     * @private
     */
    this.previousCursor_ = undefined;
  }

  if (PointerInteraction) Drag.__proto__ = PointerInteraction;
  Drag.prototype = Object.create(PointerInteraction && PointerInteraction.prototype);
  Drag.prototype.constructor = Drag;

  return Drag;
}(PointerInteraction));

/**
 * @param {import("../src/ol/MapBrowserEvent.js").default} evt Map browser event.
 * @return {boolean} `true` to start the drag sequence.
 */
function handleDownEvent(evt) {
  var map = evt.map;

  var feature = map.forEachFeatureAtPixel(evt.pixel,
    function (feature) {
      return feature;
    });

  if (feature) {
    this.coordinate_ = evt.coordinate;
    this.feature_ = feature;
  }

  return !!feature;
}


/**
 * @param {import("../src/ol/MapBrowserEvent.js").default} evt Map browser event.
 */
function handleDragEvent(evt) {
  var deltaX = evt.coordinate[0] - this.coordinate_[0];
  var deltaY = evt.coordinate[1] - this.coordinate_[1];

  var geometry = this.feature_.getGeometry();
  geometry.translate(deltaX, deltaY);

  this.coordinate_[0] = evt.coordinate[0];
  this.coordinate_[1] = evt.coordinate[1];
}


/**
 * @param {import("../src/ol/MapBrowserEvent.js").default} evt Event.
 */
function handleMoveEvent(evt) {
  if (this.cursor_) {
    var map = evt.map;
    var feature = map.forEachFeatureAtPixel(evt.pixel,
      function (feature) {
        return feature;
      });
    var element = evt.map.getTargetElement();
    if (feature) {
      if (element.style.cursor != this.cursor_) {
        this.previousCursor_ = element.style.cursor;
        element.style.cursor = this.cursor_;
      }
    } else if (this.previousCursor_ !== undefined) {
      element.style.cursor = this.previousCursor_;
      this.previousCursor_ = undefined;
    }
  }
}


/**
 * @return {boolean} `false` to stop the drag sequence.
 */
function handleUpEvent() {
  this.coordinate_ = null;
  this.feature_ = null;
  return false;
}



export default Nongye

后端主要采用django框架写的接口
后端 核心代码在这里插入图片描述

功能界面

在这里插入图片描述

说明:
左侧目录树对图层进行隐藏,影像/边界是单独图层,农作物整是一个图层,分了不同类别,这里有区别;
统计面积是统计上面农作物分类的面积,这里统计一次后写入indexdb,方便下次快速读取
点击地块左侧显示地块的详细信息,包括可查看长势图和nvdi图

如有兴趣, 能帮到您,联系我 qq:851356263 ,请说明来意,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

来了-小老弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值