1 String

  ELasticsearch 5.X之后的字段类型不再支持string,由text和keyword取代,不做说明。

 

2 text和keyword

2.1 简介

  ElasticSearch 5.0以后,string字段被拆分成两种新的数据类型: text用于全文搜索的,而keyword用于关键词搜索。

  ElasticSearch字符串将默认被同时映射成text和keyword类型

 

2.2 示例

1)新添一份数据
PUT /myindex/_doc/1
{
  "id":1,
  "name":"手机",
  "price":3888.8,
  "desc":"USA has apple China has pineapple"
}

 

2)查询mapping映射

GET /myindex/_mapping

  结果如下,以字段desc为例,它的type是text,但是为desc自动创建了一个keyword类型的字段。也就是ElasticSearch字符串将默认被同时映射成text和keyword类型

{
  "myindex" : {
    "mappings" : {
      "properties" : {
        "desc" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "id" : {
          "type" : "long"
        },
        "name" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "price" : {
          "type" : "float"
        }
      }
    }
  }
}

 

2.3 Text 与keyword的区别

Text:
  会分词,然后进行索引
  支持模糊、精确查询
  不支持聚合

keyword:
  不进行分词,直接索引
  支持模糊、精确查询
  支持聚合

 

2.4 示例

  Text会分词,然后进行索引,keyword不进行分词,直接索引

 

1)直接通过text搜索

GET /myindex/_search
{
  "query":{
    "match":{
      "desc":"USA has apple"
    }
  }
}

  结果如下,说明text会被分词,进行索引

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.970927,
    "hits" : [
      {
        "_index" : "myindex",
        "_type" : "mytype",
        "_id" : "1",
        "_score" : 0.970927,
        "_source" : {
          "id" : 1,
          "name" : "手机",
          "price" : 3888.8,
          "desc" : "USA has apple China has pineapple"
        }
      }
    ]
  }
}

 

2)通过keyword进行搜索

GET /myindex/_search
{
  "query":{
    "match":{
      "desc.keyword":"USA has apple"
    }
  }
}

  搜索结果如下,没有搜索到数据

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 0,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  }
}

 

3)再次用keyword进行搜索,搜索到一条结果

GET /myindex/_search
{
  "query":{
    "match":{
      "desc.keyword":"USA has apple China has pineapple"
    }
  }
}

  说明keyword不进行分词,整个直接进入索引

 

3 数字类型

3.1 ES支持的数字类型

 

3.2 -0.0和+0.0

  对于float、half_float和scaled_float,-0.0和+0.0是不同的值,使用term查询查找-0.0不会匹配+0.0,同样range查询中上边界是-0.0不会匹配+0.0,下边界是+0.0不会匹配-0.0

 

3.3 数字类型的选择

  对于数字类型的数据,选择以上数据类型的注意事项:

  1)在满足需求的情况下,尽可能选择范围小的数据类型。比如,某个字段的取值最大值不会超过100,那么选择byte类型即可。迄今为止吉尼斯记录的人类的年龄的最大值为134岁,对于年龄字段,short足矣。字段的长度越短,索引和搜索的效率越高。

  2)优先考虑使用带缩放因子的浮点类型

 

3.4 t添加一个带缩放因子的浮点类型示例

PUT /myindex2
{
  "mappings": {
    "properties":{
      "mathscore":{
      "type":"scaled_float",
      "scaling_factor":100
      }
    }
  }
}

  添加数据,实际上,ES内部存储的是:12.386 * 100(缩放因子) = 1238.6,然后四舍五入得到1239,所以内部真正存储的是1239

PUT /myindex2/_doc/1
{
  "mathscore":12.386
}


  查询

GET /myindex2/_search
{
  "query":{
    "match":{
      "mathscore":12.386
  }
  }
}

  查询结果

{
  "took" : 97,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "myindex2",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "mathscore" : 12.385
        }
      }
    ]
  }
}

  过查询结果可以看到,通过12.386查询出了ID为1的文档,实际上ES是将查询条件:12.386 * 100(缩放因子) = 1238.6,然后四舍五入之后到最近的长值得到1239,这样就能匹配到ID为1的文档。

  需要注意的是,虽然ES在内部做了缩放处理,但是查询返回值还是原始值(12.385)

4 Object类型

  JSON天生具有层级关系,文档会包含嵌套的对象

 

4.1 示例

PUT myindex/_doc/4
{ 
  "region": "US",
  "manager": { 
    "age":     30,
    "name": { 
      "first": "John",
      "last":  "Smith"
    }
  }
}

  上面的文档中,整体是一个JSON,JSON中包含一个manager,manager又包含一个name。最终,文档会被索引成key-value对

{
  "region":             "US",
  "manager.age":        30,
  "manager.name.first": "John",
  "manager.name.last":  "Smith"
}

 

4.2 查询

  下面两个查询都查不到结果

GET myindex/_search
{
  "query": {
    "match": {
      "manager": "John"
    }
  }
}

GET myindex/_search
{
  "query": {
    "match": {
      "manager.name": "John"
    }
  }
}

  下面的查询可以查到结果

GET myindex/_search
{
  "query": {
    "match": {
      "manager.name.first": "John"
    }
  }
}

  查询结果如下

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.13353139,
    "hits" : [
      {
        "_index" : "myindex",
        "_type" : "mytype",
        "_id" : "4",
        "_score" : 0.13353139,
        "_source" : {
          "region" : "US",
          "manager" : {
            "age" : 30,
            "name" : {
              "first" : "John",
              "last" : "Smith"
            }
          }
        }
      }
    ]
  }
}

 

5 date类型

5.1 简介

  JSON中没有date类型,es中的date可以由下面3种方式表示

  1)格式化的date字符串,例如"2018-01-01"或者"2018-01-01 12:00:00"

  2)一个long型的数字,代表从1970年1月1号0点到现在的毫秒数

  3)一个integer型的数字,代表从1970年1月1号0点到现在的秒数

  在es内部,date被转为UTC,并被存储为一个长整型数字,代表从1970年1月1号0点到现在的毫秒数

  date类型字段上的查询会在内部被转为对long型值的范围查询,查询的结果类型是字符串。

  假如插入的时候,值是"2018-01-01",则返回"2018-01-01"

  假如插入的时候,值是"2018-01-01 12:00:00",则返回"2018-01-01 12:00:00"

  假如插入的时候,值是1514736000000,则返回"1514736000000"。(进去是long型,出来是String型)

 

5.2 默认格式

  date格式可以在put mapping的时候用 format 参数指定,如果不指定的话,则启用默认格式,是"strict_date_optional_time||epoch_millis"。

  这表明只接受符合"strict_date_optional_time"格式的字符串值,或者long型数字。

 

5.2.1 strict_date_optional_time

  strict_date_optional_time是date_optional_time的严格级别,这个严格指的是年份、月份、天必须分别以4位、2位、2位表示,不足两位的话第一位需用0补齐。不满足这个格式的日期字符串是放不进es中的。

  实测strict_date_optional_time,仅支持"yyyy-MM-dd"、"yyyyMMdd"、"yyyyMMddHHmmss"、"yyyy-MM-ddTHH:mm:ss"、"yyyy-MM-ddTHH:mm:ss.SSS"、"yyyy-MM-ddTHH:mm:ss.SSSZ"格式

  不支持常用的"yyyy-MM-dd HH:mm:ss"等格式。注意,"T"和"Z"是固定的字符

 

5.2.2 epoch_millis

  epoch_millis约束值必须大于等于Long.MIN_VALUE,小于等于Long.MAX_VALUE

  date类型字段除了type参数必须指定为date外,还有一个常用的参数 format 。可以通过该参数来显式指定es接受的date格式,如果有多个的话,多个date格式需用||分隔。之后index/create/update操作时,将依次匹配,如果匹配到合适的格式,则会操作成功,并且查询时,该文档该字段也会以该格式展示。否则,操作不成功。

 

5.2.3 示例

  添加一个字段名为update_date,类型为date,格式为yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis的字段

PUT myindex/_mapping
{
      "properties": {
        "updated_date": {
          "type":   "date",
          "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
        }
      }
    
}

 

6 Array类型

  ELasticsearch没有专用的数组类型,默认情况下任何字段都可以包含一个或者多个值,但是一个数组中的值要是同一种类型。

    1)字符数组: [ “one”, “two” ]

    2)整型数组:[1,3]

    3)嵌套数组:[1,[2,3]],等价于[1,2,3]

    4)对象数组:[ { “name”: “Mary”, “age”: 12 }, { “name”: “John”, “age”: 10 }]

  注意事项

    1)动态添加数据时,数组的第一个值的类型决定整个数组的类型

    2)混合数组类型是不支持的,比如:[1,”abc”]

    3)数组可以包含null值,空数组[ ]会被当做missing field对待

 

7 binary类型

  binary类型接受base64编码的字符串,默认不存储也不可搜索

 

7.1 添加一个binary类型字段blobs

PUT myindex/_mapping
{

      "properties": {
        "blobs": {
          "type": "binary"
        }
      }
}

 

7.2 添加数据

PUT myindex/_doc/10
{
  "blobs": "U29tZSBiaW5hcnkgYmxvYg==" 
}

 

7.3 通过字段blobs查询

GET /myindex/_search
{
  "query": {
    "match":{
      "blobs":"U29"
    }
  }
}

  报错,提示binary类型的字段不支持查询

{
  "error" : {
    "root_cause" : [
      {
        "type" : "query_shard_exception",
        "reason" : "Binary fields do not support searching",
        "index_uuid" : "cSOGKPPDQF2TsecmZEY-ig",
        "index" : "myindex"
      }
    ],
    "type" : "search_phase_execution_exception",
    "reason" : "all shards failed",
    "phase" : "query",
    "grouped" : true,
    "failed_shards" : [
      {
        "shard" : 0,
        "index" : "myindex",
        "node" : "WItnUli7SN2T159XFePjQQ",
        "reason" : {
          "type" : "query_shard_exception",
          "reason" : "Binary fields do not support searching",
          "index_uuid" : "cSOGKPPDQF2TsecmZEY-ig",
          "index" : "myindex"
        }
      }
    ]
  },
  "status" : 400
}

 

8 IP类型

  ip类型的字段用于存储IPV4或者IPV6的地址

 

8.1 添加一个类型为ip的字段ip_addr

PUT myindex/_mapping
{
  "properties":{
    "ip_addr":{
    "type":"ip"
  }
  }
}

 

8.2 添加数据

PUT myindex/_doc/11
{
  "ip_addr":"192.168.128.110"
}

 

8.3 通过ip_addr精确查询

GET myindex/_search
{
  "query": {
    "match": {
      "ip_addr":"192.168.128.110"
    }
  }
}

  查询结果

{
  "took" : 574,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "myindex",
        "_type" : "mytype",
        "_id" : "11",
        "_score" : 1.0,
        "_source" : {
          "ip_addr" : "192.168.128.110"
        }
      }
    ]
  }
}

 

9 range类型

  范围类型(range)是es中比较具有特色的数据类型

 

9.1 range的几种类型

 

9.2 示例

  range类型的使用场景:比如前端的时间选择表单、年龄范围选择表单等。

  

1)创建一个Integer_range类型字段和一个date_range类型字段

  假设我们有一张会议表。我们知道实际中党政机关会议都有一个出席率的问题,需要出席率在某个点或某个区间内才能算作是有效的。所以我们的映射结构来了

PUT range_index
{
  "mappings": {
      "properties": {
        "expected_attendees": {
          "type": "integer_range"
        },
        "time_frame": {
          "type": "date_range", 
          "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
        }
      }
  }
}

 

2)添加数据

  假如这时我们需要添加一个10-20人参与,并且在2015-10-31到2015-11-01期间举行的会议

PUT range_index/_doc/1
{
  "expected_attendees" : { 
    "gte" : 10,
    "lte" : 20
  },
  "time_frame" : { 
    "gte" : "2015-10-31 12:00:00", 
    "lte" : "2015-11-01"
  }
}

 

3)查询

  我们需要查询会议时间在2015-08-01到2015-12-01之间的会议

GET range_index/_search
{
  "query" : {
    "range" : {
      "time_frame" : { 
        "gte" : "2015-08-01",
        "lte" : "2015-12-01",
        "relation" : "within" 
      }
    }
  }
}

  查询结果

{
  "took" : 84,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "range_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "expected_attendees" : {
            "gte" : 10,
            "lte" : 20
          },
          "time_frame" : {
            "gte" : "2015-10-31 12:00:00",
            "lte" : "2015-11-01"
          }
        }
      }
    ]
  }
}

 

9.3 relation

  用range query查询range类型字段时,可以额外指定一个relation参数,默认值是intersects,其他可选值有within、contains。

  intersects意思是只要文档range类型字段值指定的范围和range query指定的范围有交叉,就能查出来。

  within表示只有查询范围包含文档范围时,能查出来。

  contains表示只有文档范围包含查询范围时,能查出来

 

10 nested类型

10.1 简介

  官方定义:官方释义:这个nested类型是object一种数据类型,允许对象数组以相互独立的方式进行索引

  nested属于object类型的一种,是Elasticsearch中用于复杂类型对象数组的索引操作。Elasticsearch没有内部对象的概念,因此,ES在存储复杂类型的时候会把对象的复杂层次结果扁平化为一个键值对列表

 

10.2 示例1Object类型

  假如我们有如下order索引,包含订单的商品列表

 

10.2.1 添加数据

PUT /order/_doc/1
{
  "order_name": "xiaomi order",
  "desc": "shouji zhong de zhandouji",
  "goods_count": 3,
  "total_price": 12699,
  "goods_list": [
    {
      "name": "xiaomi PRO MAX 5G",
      "price": 4999
    },
    {
      "name": "ganghuamo",
      "price": 19
    },
    {
      "name": "shoujike",
      "price": 1999
    }
  ]
}
PUT /order/_doc/2
{
  "order_name": "Cleaning robot order",
  "desc": "shouji zhong de zhandouji",
  "goods_count": 2,
  "total_price": 12699,
  "goods_list": [
    {
      "name": "xiaomi cleaning robot order",
      "price": 1999
    },
    {
      "name": "dishwasher",
      "price": 4999
    }
  ]
}

 

10.2.2 查询

  查询订单商品中商品名称为dishwasher并且商品价格为1999的订单信息,尝试执行以下脚本

GET order/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "goods_list.name": "dishwasher"    // 条件一
          }
        },
        {
          "match": {
            "goods_list.price": 1999           // 条件二
          }
        }
      ]
    }
  }
}

  查询结果

{
  "took" : 6,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.7199211,
    "hits" : [
      {
        "_index" : "order",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.7199211,
        "_source" : {
          "order_name" : "Cleaning robot order",
          "desc" : "shouji zhong de zhandouji",
          "goods_count" : 2,
          "total_price" : 12699,
          "goods_list" : [
            {
              "name" : "xiaomi cleaning robot order",
              "price" : 1999
            },
            {
              "name" : "dishwasher",
              "price" : 4999
            }
          ]
        }
      }
    ]
  }
}

  按照bool中must的查询逻辑,两个条件都符合的数据并不存在,然而执行查询后发现返回以下结果

 

10.2.3 原因

  可以看到上述结果元数据中出现了订单数据,这和预期结果不一致。
  因为当字段值为复杂数据类型(Object、Geo-Point等)的时候,ES内部实际是以如下方式保存数据的

{
  "order_name": "Cleaning robot order",
  "desc": "shouji zhong de zhandouji",
  "goods_count": 2,
  "total_price": 12699,
  "goods_list.name":[ "alice", "cleaning", "robot", "order", "dishwasher" ],
  "goods_list.price":[ 1999, 4999 ]
}

  上述例子中goods_list中每个对象元素的属性值被扁平化存储在了数组中,此时已丢失了对应关系,因此无法保证搜索的准确

 

10.3 Nested类型

10.3.1 创建mapping

PUT order1
{
  "mappings": {
    "properties": {
      "goods_list": {
        "type": "nested",
        "properties": {
          "name": {
            "type": "text"
          }
        }
      }
    }
  }
}

 

10.3.2 添加数据

PUT /order1/_doc/1
{
  "order_name": "xiaomi order",
  "desc": "shouji zhong de zhandouji",
  "goods_count": 3,
  "total_price": 12699,
  "goods_list": [
    {
      "name": "xiaomi PRO MAX 5G",
      "price": 4999
    },
    {
      "name": "ganghuamo",
      "price": 19
    },
    {
      "name": "shoujike",
      "price": 1999
    }
  ]
}
PUT /order1/_doc/2
{
  "order_name": "Cleaning robot order",
  "desc": "shouji zhong de zhandouji",
  "goods_count": 2,
  "total_price": 12699,
  "goods_list": [
    {
      "name": "xiaomi cleaning robot order",
      "price": 1999
    },
    {
      "name": "dishwasher",
      "price": 4999
    }
  ]
}

 

10.3.3 查询

  查询订单商品中商品名称为dishwasher并且商品价格为1999的订单信息,查不到结果

  查询在外面嵌套了一个nested和path

GET /order1/_search
{
  "query": {
    "nested": {
      "path": "goods_list", 
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "goods_list.name": "dishwasher"
              }
            },
            {
              "match": {
                "goods_list.price": 1999
              }
            }
          ]
        }
      }
    }
  }
}

  再次查询订单商品中商品名称为dishwasher并且商品价格为4999的订单信息

GET /order1/_search
{
  "query": {
    "nested": {
      "path": "goods_list", 
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "goods_list.name": "dishwasher"
              }
            },
            {
              "match": {
                "goods_list.price": 4999
              }
            }
          ]
        }
      }
    }
  }
}

  查询到想要的结果

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 2.7844853,
    "hits" : [
      {
        "_index" : "order1",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 2.7844853,
        "_source" : {
          "order_name" : "Cleaning robot order",
          "desc" : "shouji zhong de zhandouji",
          "goods_count" : 2,
          "total_price" : 12699,
          "goods_list" : [
            {
              "name" : "xiaomi cleaning robot order",
              "price" : 1999
            },
            {
              "name" : "dishwasher",
              "price" : 4999
            }
          ]
        }
      }
    ]
  }
}

 

11 token_count类型

  token_count是单词计数数据类型。

  类型字段token_count实际上是一个integer,接受字符串值,对其进行分析,然后为字符串中的单词数量作为其值进行动态存储

 

1)添加一个类型为token_count的字段

PUT myindex2/_mapping
{
      "properties": {
        "music": { 
          "type": "text",
          "fields": {
            "length": { 
              "type":     "token_count",
              "analyzer": "standard"
            }
          }
        }
      }
}

 

2)添加数据

PUT myindex2/_doc/1
{ "music": "John Smith" }

PUT myindex2/_doc/2
{ "music": "Rachel Alice Williams" }

 

3)查询

GET myindex2/_search
{
  "query": {
    "term": {
      "music.length": 3 
    }
  }
}

  查询结果

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "myindex2",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "music" : "Rachel Alice Williams"
        }
      }
    ]
  }
}

 

12 geo point 类型

  地理位置信息类型用于存储地理位置信息的经纬度

 

12.1 es对空间地理的支持

  参考:https://zhuanlan.zhihu.com/p/378770937

  官网描述

  可以看到,官网中提供了两种类型的地理数据,分别是基于经纬度的geo_point数据类型 和 基于GeoJson的geo_shape数据类型,并且geo_shape数据类型支持点、线、圆、多边形、多多边形等复杂的地理形状;

  同时,ES支持的空间地理的语法一共有4种,分别如下:

  1)geo_bounding_box

  2)geo_distance

  3)geo_polygon

  4)geo_shape

 

12.2 数据准备

1)先创建一个mapping结构

  用于存储经纬度信息,定义存储空间地理信息的字段的类型为 geo_point,mapping映射结构如下

PUT /location
{
  "mappings": {
    "properties": {
      "lo":{
        "type": "geo_point"
      }
    }
  }
}

 

2)创建好mapping映射结构后,再来批量插入一些经纬度数据到ES中,DSL语句如下

POST /location/_doc/1
{
  "lo":{
    "lat":50,
    "lon":50
  }
}

POST /location/_doc/2
{
  "lo":{
    "lat":50,
    "lon":-50
  }
}

POST /location/_doc/3
{
  "lo":{
    "lat":-50,
    "lon":-50
  }
}

POST /location/_doc/4
{
  "lo":{
    "lat":-50,
    "lon":50
  }
}

 

12.3 ES的geo_bounding_box语法

12.3.1 简介

  geo_bounding_box语法又称为地理坐标盒模型,在当前语法中,只需选择一个矩阵范围(输入矩阵的左上角的经纬度和矩阵的右下角的经纬度,构建成为一个矩阵),即可计算出当前矩阵中符合条件的元素;

  用通俗易懂的话讲,就是给定两个坐标,通过这两个坐标形成对角线,平行于地球经纬度从而得到的一个矩阵。采用geo_bounding_box语法可以得到坐落于当前矩阵中的元素的信息;

  假设我这边给定两个坐标,分别是北京市顺义西站(116.498353,40.187328) 和 北京市首都机场(116.610461,40.084509),这样我们就得到了一个矩阵,如下图所示,通过两个坐标确定一个矩阵

   ES的geo_bounding_box语法有很多种查询方式,但是需要注意的是我们要确定好哪个是左上角的坐标,哪个是右下角的坐标,并且这两个坐标不能互换

 

12.3.2 基于经纬度属性的DSL语法

GET location/_search
{
  "query": {
    "bool": {
      "must": {
        "match_all": {}
      },
      "filter": {
        "geo_bounding_box": {
          "lo": {
            "top_left": {
              "lat": 51,
              "lon": -51
            },
            "bottom_right": {
              "lat": -51,
              "lon": 51
            }
          }
        }
      }
    }
  }
}

查询结果

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "location",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "lo" : {
            "lat" : 50,
            "lon" : "50"
          }
        }
      },
      {
        "_index" : "location",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "lo" : {
            "lat" : 50,
            "lon" : "-50"
          }
        }
      },
      {
        "_index" : "location",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "lo" : {
            "lat" : -50,
            "lon" : -50
          }
        }
      },
      {
        "_index" : "location",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 1.0,
        "_source" : {
          "lo" : {
            "lat" : -50,
            "lon" : 50
          }
        }
      }
    ]
  }
}

 

12.3.3 基于经纬度数组的DSL语法

# 需要注意的是,数组形式的,经纬度顺序需调换一下
GET location/_search
{
  "query": {
    "geo_bounding_box": {
      "locationStr": {
        "top_left": [116.498353, 40.187328],
        "bottom_right": [116.610461, 40.084509]
      }
    }
  }
}

 

12.3.4 基于经纬度字符串的DSL语法

GET location/_search
{
  "query": {
    "geo_bounding_box": {
      "locationStr": {
        "top_left": "40.187328, 116.498353",
        "bottom_right": "40.084509,116.610461"
      }
    }
  }
}

 

12.3.5 基于经纬度边界框WKT的DSL语法

GET location/_search
{
  "query": {
    "geo_bounding_box": {
      "locationStr": {
         "wkt": "BBOX (116.498353,116.610461,40.187328,40.084509)"
      }
    }
  }
}

 

12.3.6 基于经纬度GeoHash的DSL语法

# 关于GeoHash可以参考两个网址
# 全球GeoHash地图 http://geohash.gofreerange.com/
# GeoHash坐标在线转换 http://geohash.co/
GET location/_search
{
  "query": {
    "geo_bounding_box": {
      "locationStr": {
        "top_left": "wx4udgz",
        "bottom_right": "wx4uj91"
      }
    }
  }
}

 

12.3.7 基于经纬度顶点属性的DSL语法

GET location/_search
{
  "query": {
    "geo_bounding_box": {
      "locationStr": {
        "top": 40.187328,
        "left": 116.498353,
        "bottom": 40.084509,
        "right": 116.610461
      }
    }
  }
}

 

12.4 ES的geo_distance语法

12.4.1 简介

  ES中的geo_distance语法与Redis中的georadius语法类似,通过给定一个坐标和半径,圈出圆内的点。在ES可以定义一些排序规则返回召回结果集数据与当前坐标的距离,Redis中默认返回了距离;

  与geo_bounding_box语法类似,geo_distance语法也有多种查询方式,如 经纬度属性、经纬度数组、经纬度字符串、GeoHash等,下面就简单的以 经纬度字符串和GeoHash为例进行演示,重新选定坐标,以公司目前所在位置为例,理想总部经纬度为:(116.5864,40.174697),查询公司5km范围内的建筑

 

12.4.2 查询

GET location/_search
{
  "query": {
    "geo_distance": {
      "distance": "200km",
      "lo": "49,49"
    }
  },
  "sort": 
    {
      "_geo_distance": {
        "lo": "49,49",
        "order": "asc",
        "unit": "km",
        "distance_type": "plane"
      }
    }
  
}

  查询结果

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "location",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : null,
        "_source" : {
          "lo" : {
            "lat" : 50,
            "lon" : "50"
          }
        },
        "sort" : [
          132.5873792195451
        ]
      }
    ]
  }
}

  返回结果集的每一条数据中,都包含一个 sort字段,里面的数据表示的是当前建筑坐标与当前用户坐标相差的直线距离,单位为km,单位是在上述DSL语句中指定的,

  采用GeoHash方式查询的DSL语句如下,没有指定sort时,默认是没有返回坐标距离的

GET location/_search
{
  "query": {
    "geo_distance": {
      "distance": "200km",
      "lo": "49,49"
    }
  }
  
}

 

12.5 ES的geo_polygon语法

12.5.1 简介

  ES的geo_polygon语法,可以通过指定多个坐标点,从而构成一个多边形,然后从当前多边形中召回坐落其中的元素进行召回;在当前语法中,最少需要3个坐标,从而构成一个多边形;

  ES的geo_point结构的3中语法,分别覆盖了矩形、圆、多边形的空间地理的召回;

  为了方便演示,我这边在ES中新增一条记录,记录公司的所在位置经纬度为:(116.5864,40.174697)

  然后可以指定多个个坐标,这里指定三个坐标,形成一个三角形

GET location/_search
{
  "query": {
    "bool": {
      "must": {
        "match_all": {}
      },
      "filter": {
        "geo_polygon": {
          "lo": {
            "points": [
              { "lat": 60, "lon": 60 },
              { "lat": 0, "lon": 80 },
              { "lat": 25, "lon": -25 }
            ]
          }
        }
      }
    }
  }
}

  查询结果

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "location",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "lo" : {
            "lat" : 50,
            "lon" : "50"
          }
        }
      }
    ]
  }
}

   ES的geo_polygon语法也支持多种语法,如 经纬度数组、经纬度字符串、GeoHash值等;

 

13 geo_shap

13.1 简介

  通常情况,我们使用一个经纬度坐标表示一个店铺的位置、一个用户的位置,经纬度在地图上仅仅表示一个点,有时候需要表示一个区域,例如:停车场、商场、学校等等,这些区域拥有各种各样的形状,包括:圆形、多边形等等。 在geo_shape中,点作为一种特殊的形状,geo_shape可以存储一个点。

  ES中存储地理形状的数据类型为: geo_shape

  geo_shape支持存储的常用形状数据如下:

  • 点(point)
  • 圆形(circle)
  • 矩形(envelope)
  • 多边形 (polygon)

 

13.2 geo_shape支持的格式

  官网描述

   geo_shape支持GeoJsonWKT(Well-Known Text)格式存储空间形状数据。建议使用wkt

  

 

13.3 格式说明

13.3.1 GeoJson格式数据
  GeoJson格式参考官方网站:https://geojson.org/
  在es中则只需要存储其geometry的属性值即为geo_shape的值

{
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [125.6, 10.1]
  },
  "properties": {
    "name": "Dinagat Islands"
  }
}

 

13.3.2 wkt Well-Known Text (WKT)格式数据

POINT (-77.03653 38.897676) 
LINESTRING (-77.03653 38.897676,-77.009051 38.889939) 
POLYGON ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0)) 
MULTIPOINT (102.0 2.0, 103.0 2.0) 
MULTILINESTRING ((102.0 2.0, 103.0 2.0, 103.0 3.0, 102.0 3.0),(100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8)) 
MULTIPOLYGON (((102.0 2.0, 103.0 2.0, 103.0 3.0, 102.0 3.0, 102.0 2.0)), ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0), (100.2 0.2, 100.8 0.2, 100.8 0.8, 100.2 0.8, 100.2 0.2))) 
GEOMETRYCOLLECTION (POINT (100.0 0.0), LINESTRING (101.0 0.0, 102.0 1.0)) 
BBOX (100.0, 102.0, 2.0, 0.0)

 

13.4 存储示例

1)定义geo_shape类型映射

PUT /example
{
    "mappings": {
        "properties": {
            "location": {
                "type": "geo_shape" 
            }
        }
    }
}

 

2)存储一个点

  数组写法

POST /example/_doc
{
    "location" : {
        "type" : "point",
        "coordinates" : [-77.03653, 38.897676] 
    }
}

  字符串写法

POST /example/_doc
{
    "location" : "POINT (-77.03653 38.897676)"
}

 

3)存储一个多边形

  注意:最后一个坐标点,要跟第一个坐标点相同,这样多边形才能形成闭合

  数组写法

POST /example/_doc
{
  "location": {
    "type": "polygon",
    "coordinates": [ 
      [ 
        [100, 0], 
        [101, 0],
        [101, 1],
        [100, 1],
        [100, 0] 
      ]
    ]
  }
}

  字符串写法

POST /example/_doc
{
    "location" : "POLYGON ((100.0 0.0, 101.0 0.0, 101.0 1.0, 100.0 1.0, 100.0 0.0))"
}

 

13.5 geo_shape地理形状搜索

  官网介绍

  当索引的字段类型定义为geo_shape之后,我们就可以通过geo_shape实现图形搜索。

 

13.5.1 图形搜索包含关系
1)intersects - 查询的形状与返回索引的形状有重叠(默认), 即图形有交集则匹配。
2)disjoint - 查询的形状与返回索引的形状完全不重叠。
3)within - 查询的形状包含返回索引的形状。
4)contains - 返回索引的形状包含查询的形状

 

13.5.2 搜索格式

GET /example/_search
{
    "query":{
        "bool": { // 布尔组合查询语句
            "must": {
                "match_all": {} // 这里设置其他查询条件,直接匹配全部文档
            },
            "filter": { // 地理信息搜索,通常不参与相关性计算,所以使用filter包裹起来
                "geo_shape": { // geo_shape搜索语句
                    "location": { // 图形数据存储在location字段
                    "relation": "within" // 设置图形搜索类型,这里设置为包含关系
                    "shape": {   //这个可以理解为其实就是geojson的geometry的值
                        "type": "polygon",  // 设置图形类型,各种图形格式参考geo_shape的数据格式支持的图形类型
                        "coordinates": [
                                [
                                    [
                                        104.0396387972344,
                                        30.59613123035072
                                    ],
                                    [
                                        104.0393476378968,
                                        30.59549712177650
                                    ],
                                    [
                                        104.0396387858758,
                                        30.59638313574942
                                    ],
                                    [
                                        104.0396387972344,
                                        30.59613123035072
                                    ]
                                ]
                            ]
                        }
                    }
                }
            }
        }
    }
}