程序员找房子的正确姿势(附上源码)

事情起源

由于最近部门的办公地点要搬迁了,我租的房子离新场地有足足25公里,而且我对象最近也换了部门,我们以前租的房子在通勤上已经满足不了需求了。好在11月底我们的房子就到期了,所以租房子就被提上日程。(感慨,买房子有鸡毛用啊。)

我对象的办公地点和我新的办公点的距离开车是15公里,地铁是18公里,所以木的办法,只能在中间地带找个房子了。于是乎,我打开的了地图软件,打算把沿途的小区都一个一个记录下来,然后去计算通勤的性价比。

image.png

在地图软件上找小区倒是挺简单的,十几分钟我就记了几十个小区,然后我就做了个Excel表格,打算把各个小区到我和我对象办公点的通勤时间、距离都记录下,考虑到多种出勤方式,要统计包括了公交地铁、驾车、骑行。

于是乎,我傻傻的打开地图软件,一个一个导航,记录。当我进行到第十几个小区的时候,我吐了。

于是乎我想到了AI,于是乎我就去问AI,然后AI这么回答了我

image.png
可以可以,AI回答的一点毛病没有,有理有据令人信服。

由于我实在不想像SB一样去一个一个查了,于是乎我打开了高德的官网,希望能够得到一点启发。你别说,你还真别说,我还真就发现了一个解决方案。

image.png

这不就妥妥的能把问题解决了嘛,于是乎我新建了一个html文件,开始了coding。

我的需求

  • 我需要能够输入多个地址、然后选择两个目标地点,用表格展示出距离、时间等信息
  • 我需要能够生成Excel文件,方便我记录
  • 我需要能够生成图表,能够更直观的看到结果

开发思路

  • 调用高德API,规划出A地点到B和C的路径,然后整合返回数据,插入表格
  • 利用xlsx,生成Excel
  • 用eCharts画一个柱状折线图

开发中遇到的问题

  • 首先就高德Key每天的调用次数限制,不支持我频繁的搜,按理说一天应该5000次啊,但是调了一百多次就会提醒我超了,但是过会又好了
  • 由于我是sdk加载的高德依赖,所以三种出行方式的插件会用冲突,所以我只能加载一个插件就改名存以下,再加载另一个

image.png

  • 由于高德API是异步调用,不支持同步,多并发调用多次会出现有的请求失效,于是乎我想到了用Generator去控制每次请求结束再调用下一次。

成果演示

企业微信截图_17285486793009.png

企业微信截图_17285506996476.png

成果展示

这里是代码片段,可以执行下试试水,用的是我的key啊(给个赞呗),有可能会调用次数超了

遗留

生成Excel那个我没写完,因为现在已经满足我的需求了。如果你希望用的话,稍微改下我的代码就好了

源码

拷贝就能用哈
你只需要去申请一个高德官网完成开发者认证然后去申请自己的应用,把key和token加上就可以了 传送门

image.png

html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta
      name="viewport"
      content="initial-scale=1.0, user-scalable=no, width=device-width"
    />
    <link
      rel="stylesheet"
      href="https://a.amap.com/jsapi_demos/static/demo-center/css/demo-center.css"
    />
    <script src="https://a.amap.com/jsapi_demos/static/demo-center/js/demoutils.js">script>
    <script type="text/javascript">
      window._AMapSecurityConfig = {
        securityJsCode: "你的高德Token",
      };
    script>
    <script
      type="text/javascript"
      src="https://webapi.amap.com/maps?v=2.0&key=你的高德Key"
    >script>
    <script
      type="text/javascript"
      src="https://cache.amap.com/lbs/static/addToolbar.js"
    >script>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js">script>
    
    <link
      rel="stylesheet"
      href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"
    />
    
    <script src="https://unpkg.com/element-ui/lib/index.js">script>
    <script src="https://unpkg.com/xlsx@0.17.0/dist/xlsx.full.min.js">script>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js">script>
  head>

  <body>
    <div id="container" style="display: none">div>
    <div id="panel" style="display: none">div>
    <div id="vueContainer" style="padding: 16px">
      <table id="myTable" style="display: none">
        <thead>
          <tr>
            <th>小区名称th>
            <th>{{formData.target1}}最短距离th>
            <th>{{formData.target1}}最快用时th>
            <template v-if="formData.target2">
              <th>{{formData.target2}}最短距离th>
              <th>{{formData.target2}}最快用时th>
            template>
          tr>
        thead>
        <tbody>
          <tr v-for="item in excelData">
            <td>{{item.name}}td>
            <td>{{item.distance1}}td>
            <td>{{item.time1}}td>
            <template v-if="formData.target2">
              <td>{{item.distance2}}td>
              <td>{{item.time2}}td>
            template>
          tr>
        tbody>
      table>
      <el-form :model="formData">
        <el-form-item label="所在城市">
          <el-input v-model="formData.city" />
        el-form-item>
        <el-form-item label="起点">
          <el-input type="textarea" :rows="5" v-model="formData.areas" />
        el-form-item>
        <el-form-item label="终点1">
          <el-input v-model="formData.target1" />
        el-form-item>
        <el-form-item label="终点2">
          <el-input v-model="formData.target2" />
        el-form-item>
        <el-form-item label="出行方式">
          <el-select v-model="formData.type">
            <el-option value="Transfer" label="公交地铁">el-option>
            <el-option value="Driving" label="驾车">el-option>
            <el-option value="Riding" label="骑行">el-option>
          el-select>
        el-form-item>
      el-form>
      <el-button type="primary" @click="search" :disabled="loading">
        查询
      el-button>
      <el-button @click="exportToExcel">生成Excelel-button>
      <el-table border :data="dataList">
        <el-table-column prop="from" label="小区名称">el-table-column>
        <el-table-column :label="formData.target1 || '-'">
          <el-table-column prop="minDistance1" label="最短距离">
            <template #default="{row}">
              {{row[formData.target1].minDistance}}
            template>
          el-table-column>
          <el-table-column prop="minTime1" label="最短时间">
            <template #default="{row}">
              {{row[formData.target1].minTime}}
            template>
          el-table-column>
          <el-table-column prop="name" label="具体路径">
            <template #default="{row}">
              <div
                v-for="(plan,pIndex) in row[formData.target1].plans"
                :key="pIndex"
              >
                方案{{pIndex +1}}: {{plan.instructions?.join(',')}}
                <b style="color: burlywood">
                  总用时: {{plan.timeTotal || plan.time || 0}}
                  总距离:{{plan.distanceTotal || plan.distance || 0}}
                b>
                <el-tag type="primary" v-if="plan.shortest"> 最短距离 el-tag>
                <el-tag type="danger" v-if="plan.fastest">最快用时el-tag>
              div>
            template>
          el-table-column>
        el-table-column>
        <el-table-column :label="formData.target2 || '-'">
          <el-table-column prop="minDistance2" label="最短距离">
            <template #default="{row}">
              {{row[formData.target2].minDistance}}
            template>
          el-table-column>
          <el-table-column prop="minTime2" label="最短时间">
            <template #default="{row}">
              {{row[formData.target2].minTime}}
            template>
          el-table-column>
          <el-table-column prop="name" label="具体路径">
            <template #default="{row}">
              <div
                v-for="(plan,pIndex) in row[formData.target2].plans"
                :key="pIndex"
              >
                方案{{pIndex +1}}: {{plan.instructions?.join(',')}}
                <b style="color: burlywood">
                  总用时: {{plan.timeTotal || plan.time || 0}}
                  总距离:{{plan.distanceTotal || plan.distance || 0}}
                b>
                <el-tag type="primary" v-if="plan.shortest"> 最短距离 el-tag>
                <el-tag type="danger" v-if="plan.fastest">最快用时el-tag>
              div>
            template>
          el-table-column>
        el-table-column>
      el-table>
      <div
        id="chart-container"
        style="margin-top: 32px; height: 500px; overflow: hidden"
      >div>
    div>
    <script type="text/javascript">
      map = new AMap.Map("container", {
        resizeEnable: true,
        zoom: 13,
      });
    script>
    <script
      type="text/javascript"
      src="https://webapi.amap.com/maps?v=2.0&key=你的高德Key&plugin=AMap.Transfer"
    >script>
    <script type="text/javascript">
      TransferAMap = AMap;
    script>
    <script
      type="text/javascript"
      src="https://webapi.amap.com/maps?v=2.0&key=你的高德Key&plugin=AMap.Driving"
    >script>
    <script type="text/javascript">
      DrivingAMap = AMap;
    script>
    <script
      type="text/javascript"
      src="https://webapi.amap.com/maps?v=2.0&key=你的高德Key&plugin=AMap.Riding "
    >script>
    <script type="text/javascript">
      RidingAMap = AMap;
    script>
    <script type="text/javascript">
      const vm = new Vue({
        el: "#vueContainer",
        data() {
          return {
            myChart: null,
            loading: false,
            dataList: [],
            formData: {
              city: "杭州",
              type: "Transfer",
              areas: "",
              target1: "平安金融中心",
              target2: "金沙中心",
            },
          };
        },
        computed: {
          usefulAreas() {
            return this.formData.areas.split("n").filter((item) => !!item);
          },
          excelData() {
            return;
          },
        },
        mounted() {
          const dom = document.getElementById("chart-container");
          this.myChart = echarts.init(dom, null, {
            renderer: "canvas",
            useDirtyRect: false,
          });
          window.addEventListener("resize", this.myChart.resize);
        },
        methods: {
          formatDistance(distance) {
            return (distance / 1000).toFixed(2) + "公里";
          },
          formatTime(time) {
            return Math.ceil(time / 60) + "分钟";
          },
          searchFn(source, from) {
            return new Promise((resolve, reject) => {
              switch (this.formData.type) {
                case "Transfer":
                  
                  const transfer = new TransferAMap.Transfer({
                    map,
                    city: this.formData.city,
                    panel: "panel",
                    policy: TransferAMap.TransferPolicy.LEAST_TIME, 
                  });
                  transfer.search(source, (status, result) => {
                    console.log(result);
                    
                    if (status === "complete") {
                      const plans = result.plans.map((plan) => {
                        let distanceTotal = 0;
                        let timeTotal = 0;
                        const instructions = plan.segments.map(
                          ({ distance, time, instruction }) => {
                            distanceTotal += distance;
                            timeTotal += time;
                            return instruction;
                          }
                        );
                        return {
                          distanceTotal,
                          timeTotal,
                          instructions,
                        };
                      });
                      plans.forEach((plan, index) => {
                        const others = plans.filter((plan, i) => index !== i);
                        plan.fastest = others.every(
                          (other) => other.timeTotal > plan.timeTotal
                        );
                        plan.shortest = others.every(
                          (other) => other.distanceTotal > plan.distanceTotal
                        );
                      });
                      const minDistance = plans.reduce((a, b) =>
                        a.distanceTotal < b.distanceTotal ? a : b
                      ).distanceTotal;
                      const minTime = plans.reduce((a, b) =>
                        a.timeTotal < b.timeTotal ? a : b
                      ).timeTotal;
                      resolve({
                        from,
                        minDistance: this.formatDistance(minDistance),
                        minTime: this.formatTime(minTime),
                        plans: plans.map((plan) => ({
                          ...plan,
                          timeTotal: this.formatTime(plan.timeTotal),
                          distanceTotal: this.formatDistance(
                            plan.distanceTotal
                          ),
                        })),
                      });
                    } else {
                      log.error("公交路线数据查询失败" + result);
                      reject();
                    }
                  });
                  break;
                case "Driving":
                  const driving = new DrivingAMap.Driving({
                    map: map,
                    city: this.formData.city,
                    panel: "panel",
                  });
                  driving.search(source, (status, result) => {
                    console.log(result);
                    if (status === "complete") {
                      const minDistance = result.routes.reduce((a, b) =>
                        a.distance < b.distance ? a : b
                      ).distance;
                      const minTime = result.routes.reduce((a, b) =>
                        a.time < b.time ? a : b
                      ).time;
                      resolve({
                        from,
                        minDistance: this.formatDistance(minDistance),
                        minTime: this.formatTime(minTime),
                        plans: result.routes.map((item) => {
                          return {
                            distance: this.formatDistance(item.distance),
                            time: this.formatTime(item.time),
                          };
                        }),
                      });
                    } else {
                      log.error("公交路线数据查询失败" + result);
                      reject();
                    }
                  });
                  break;
                case "Riding":
                  const riding = new RidingAMap.Riding({
                    map: map,
                    city: this.formData.city,
                    panel: "panel",
                  });
                  riding.search(source, (status, result) => {
                    if (status === "complete") {
                      const minDistance = result.routes.reduce((a, b) =>
                        a.distance < b.distance ? a : b
                      ).distance;
                      const minTime = result.routes.reduce((a, b) =>
                        a.time < b.time ? a : b
                      ).time;
                      resolve({
                        from,
                        minDistance: this.formatDistance(minDistance),
                        minTime: this.formatTime(minTime),
                        plans: result.routes.map((item) => {
                          return {
                            distance: this.formatDistance(item.distance),
                            time: this.formatTime(item.time),
                          };
                        }),
                      });
                    } else {
                      log.error("骑行路线数据查询失败" + result);
                      reject();
                    }
                  });
                  break;
              }
            });
          },
          generateRequests: function* (arr) {
            for (let from of arr) {
              yield from;
            }
          },
          async search() {
            this.loading = true;
            this.dataList = [];
            const requests = this.generateRequests(this.usefulAreas);
            for (let from of requests) {
              
              let result1 = await this.searchFn(
                [
                  { keyword: from, city: this.formData.city },
                  {
                    keyword: this.formData.target1,
                    city: this.formData.city,
                  },
                ],
                from
              );
              let result2 = await this.searchFn(
                [
                  { keyword: from, city: this.formData.city },
                  {
                    keyword: this.formData.target2,
                    city: this.formData.city,
                  },
                ],
                from
              );
              this.dataList.push({
                from,
                [this.formData.target1]: result1,
                [this.formData.target2]: result2,
              });
              console.log(this.dataList);
            }
            this.loading = false;
            this.draw();
          },
          exportToExcel() {
            var table = document.getElementById("myTable");
            var wb = XLSX.utils.table_to_book(table, { sheet: "Sheet JS" });
            
            XLSX.writeFile(wb, "租房.xlsx");
          },
          draw() {
            const colors = ["#5470C6", "#91CC75", "#EE6666", "#F9764F"];
            const option = {
              color: colors,
              tooltip: {
                trigger: "axis",
                axisPointer: {
                  type: "cross",
                },
              },
              grid: {
                right: "20%",
              },
              toolbox: {
                feature: {
                  dataView: { show: true, readOnly: false },
                  restore: { show: true },
                  saveAsImage: { show: true },
                },
              },
              legend: {
                data: [this.formData.target1, this.formData.target2],
              },
              xAxis: [
                {
                  type: "category",
                  axisTick: {
                    alignWithLabel: true,
                  },
                  
                  data: this.usefulAreas,
                },
              ],
              yAxis: [
                {
                  type: "value",
                  name: this.formData.target1 + "(距离)",
                  position: "left",
                  alignTicks: true,
                  axisLine: {
                    show: true,
                    lineStyle: {
                      color: colors[0],
                    },
                  },
                  axisLabel: {
                    formatter: (value) => `${value / 1000}公里`,
                  },
                },
                {
                  type: "value",
                  name: this.formData.target2 + "(距离)",
                  position: "left",
                  alignTicks: true,
                  offset: 80,
                  axisLine: {
                    show: true,
                    lineStyle: {
                      color: colors[1],
                    },
                  },
                  axisLabel: {
                    formatter: (value) => `${value / 1000}公里`,
                  },
                },
                {
                  type: "value",
                  name: this.formData.target1 + "(时间)",
                  position: "right",
                  alignTicks: true,
                  axisLine: {
                    show: true,
                    lineStyle: {
                      color: colors[2],
                    },
                  },
                  axisLabel: {
                    formatter: (value) => `${value / 60}分钟`,
                  },
                },
                {
                  type: "value",
                  name: this.formData.target2 + "(时间)",
                  position: "right",
                  alignTicks: true,
                  axisLine: {
                    show: true,
                    lineStyle: {
                      color: colors[3],
                    },
                  },
                  axisLabel: {
                    formatter: (value) => `${value / 60}分钟`,
                  },
                },
              ],
              series: [
                {
                  name: this.formData.target1 + "(距离)",
                  type: "bar",
                  data: this.dataList.map((item) => {
                    return (
                      parseFloat(item[this.formData.target1].minDistance) * 1000
                    );
                  }),
                },
                {
                  name: this.formData.target2 + "(距离)",
                  type: "bar",
                  yAxisIndex: 1,
                  data: this.dataList.map((item) => {
                    return (
                      parseFloat(item[this.formData.target2].minDistance) * 1000
                    );
                  }),
                },
                {
                  name: this.formData.target1 + "(时间)",
                  type: "line",
                  yAxisIndex: 2,
                  data: this.dataList.map((item) => {
                    return parseFloat(item[this.formData.target1].minTime) * 60;
                  }),
                },
                {
                  name: this.formData.target2 + "(时间)",
                  type: "line",
                  yAxisIndex: 2,
                  data: this.dataList.map((item) => {
                    return parseFloat(item[this.formData.target2].minTime) * 60;
                  }),
                },
              ],
            };

            if (option && typeof option === "object") {
              this.myChart.setOption(option);
            }
          },
        },
      });
    script>
  body>
html>

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.shuli.cc/?p=21457,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?