class MapPLZ::GeoItem

internal map object record

Public Class Methods

centroid(geo_item) click to toggle source
# File lib/mapplz.rb, line 865
def self.centroid(geo_item)
  # generate centroid if possible and not already existing
  if geo_item.key?(:path) && !geo_item.centroid
    coordinates = geo_item[:path].clone

    # centroid calculation from https://code.google.com/p/tokland/source/browse/trunk/centroid
    consecutive_pairs = (coordinates + [coordinates.first]).each_cons(2)
    area = 0.5 * consecutive_pairs.map do |(x0, y0), (x1, y1)|
      (x0 * y1) - (x1 * y0)
    end
    area.inject(:+)

    consecutive_pairs.map! do |(x0, y0), (x1, y1)|
      cross = (x0 * y1 - x1 * y0)
      [(x0 + x1) * cross, (y0 + y1) * cross]
    end
    (center_lat, center_lng) = consecutive_pairs.transpose.map do |cs|
      cs.inject(:+) / (6 * area)
    end

    geo_item[:centroid] = Digest::SHA256.digest(geo_item[:path].to_s)
    geo_item[:lat] = center_lat
    geo_item[:lng] = center_lng
  end
end
new(db = nil) click to toggle source
# File lib/mapplz.rb, line 713
def initialize(db = nil)
  db = { type: 'array', client: nil } if db.nil?
  @db = db
  @db_type = db[:type]
  @db_client = db[:client]
end

Public Instance Methods

centroid() click to toggle source
# File lib/mapplz.rb, line 824
def centroid
  # verify up-to-date centroid exists
  path_hash = Digest::SHA256.digest(self[:path].to_s)
  (key?(:path) && key?(:centroid) && self[:centroid] == path_hash)
end
delete_item() click to toggle source
# File lib/mapplz.rb, line 749
def delete_item
  if @db_type == 'array'
    keys.each do |key|
      delete(key)
    end
  elsif @db_type == 'mongodb'
    # update record in database
    @db[:client].remove(_id: BSON::ObjectId(self[:_id]))
  elsif @db_type == 'postgis'
    @db_client.exec("DELETE FROM mapplz WHERE id = #{self[:id]}")
  elsif @db_type == 'spatialite'
    @db_client.execute("DELETE FROM mapplz WHERE id = #{self[:id]}")
  end
end
distance_from(user_geo) click to toggle source
# File lib/mapplz.rb, line 830
def distance_from(user_geo)
  GeoItem.centroid(self)
  user_geo = MapPLZ.standardize_geo(user_geo)[0]
  GeoItem.centroid(user_geo)

  Geokdtree::Tree.distance([user_geo[:lat], user_geo[:lng]], [self[:lat], self[:lng]])
end
inside?(user_geo) click to toggle source
# File lib/mapplz.rb, line 838
def inside?(user_geo)
  GeoItem.centroid(self)

  # accept [point1, point2, point3, point1] as a polygon search area
  if user_geo.is_a?(Array) && user_geo[0].is_a?(Array) && !user_geo[0][0].is_a?(Array)
    user_geo = [user_geo]
  end

  user_geo = MapPLZ.standardize_geo(user_geo)[0]
  path_pts = user_geo[:path]
  path_pts = path_pts[0] if user_geo[:type] == 'polygon'

  # point in polygon from http://jakescruggs.blogspot.com/2009/07/point-inside-polygon-in-ruby.html
  c = false
  i = -1
  j = path_pts.size - 1
  while (i += 1) < path_pts.size
    if (path_pts[i][0] <= self[:lat] && self[:lat] < path_pts[j][0]) || (path_pts[j][0] <= self[:lat] && self[:lat] < path_pts[i][0])
      if self[:lng] < (path_pts[j][1] - path_pts[i][1]) * (self[:lat] - path_pts[i][0]) / (path_pts[j][0] - path_pts[i][0]) + path_pts[i][1]
        c = !c
      end
    end
    j = i
  end
  c
end
save!() click to toggle source
# File lib/mapplz.rb, line 720
def save!
  # update record in database
  if @db_type == 'mongodb'
    save_obj = clone
    save_obj.delete(:_id)
    save_obj.delete(:lat)
    save_obj.delete(:lng)
    save_obj.delete(:path)
    save_obj.delete(:centroid)
    save_obj.delete(:type)
    save_obj[:geo] = JSON.parse(to_geojson)['geometry']
    @db[:client].update({ _id: BSON::ObjectId(self[:_id]) }, save_obj)
  elsif @db_type == 'postgis'
    geojson_props = (JSON.parse(to_geojson)['properties'] || {})
    @db_client.exec("UPDATE mapplz SET geom = ST_GeomFromText('#{to_wkt}'), properties = '#{geojson_props.to_json}' WHERE id = #{self[:id]}") if @db_type == 'postgis'
  elsif @db_type == 'spatialite'
    updaters = []
    keys.each do |key|
      next if [:id, :lat, :lng, :path, :type, :centroid].include?(key)
      updaters << "#{key} = '#{self[key]}'" if self[key].is_a?(String)
      updaters << "#{key} = #{self[key]}" if self[key].is_a?(Integer) || self[key].is_a?(Float)
    end
    updaters << "geom = AsText('#{to_wkt}')"
    if updaters.length > 0
      @db_client.execute("UPDATE mapplz SET #{updaters.join(', ')} WHERE id = #{self[:id]}")
    end
  end
end
to_geojson() click to toggle source
# File lib/mapplz.rb, line 781
def to_geojson
  if key?(:properties)
    property_list = { properties: self[:properties] }
  else
    property_list = clone
    property_list.delete(:lat)
    property_list.delete(:lng)
    property_list.delete(:path)
    property_list.delete(:type)
    property_list.delete(:centroid)
  end

  output_geo = {
    type: 'Feature',
    properties: property_list
  }

  if self[:type] == 'point'
    # point
    output_geo[:geometry] = {
      type: 'Point',
      coordinates: [self[:lng], self[:lat]]
    }
  elsif self[:type] == 'polyline'
    # line
    output_geo[:geometry] = {
      type: 'LineString',
      coordinates: MapPLZ.flip_path(self[:path])
    }
  elsif self[:type] == 'polygon'
    # polygon
    rings = self[:path].clone
    rings.map! do |ring|
      MapPLZ.flip_path(ring)
    end
    output_geo[:geometry] = {
      type: 'Polygon',
      coordinates: rings
    }
  end
  output_geo.to_json
end
to_wkt() click to toggle source
# File lib/mapplz.rb, line 764
def to_wkt
  if self[:type] == 'point'
    geom = "POINT(#{self[:lng]} #{self[:lat]})"
  elsif self[:type] == 'polyline'
    linestring = self[:path].map do |path_pt|
      "#{path_pt[1]} #{path_pt[0]}"
    end
    geom = "LINESTRING(#{linestring.join(', ')})"
  elsif self[:type] == 'polygon'
    linestring = self[:path][0].map do |path_pt|
      "#{path_pt[1]} #{path_pt[0]}"
    end
    geom = "POLYGON((#{linestring.join(', ')}))"
  end
  geom
end