github.com/wtfutil/wtf@v0.43.0/modules/krisinformation/client.go (about)

     1  package krisinformation
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"net/http"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/wtfutil/wtf/logger"
    12  	"github.com/wtfutil/wtf/utils"
    13  )
    14  
    15  const (
    16  	krisinformationAPI = "https://api.krisinformation.se/v2/feed?format=json"
    17  )
    18  
    19  type Krisinformation []struct {
    20  	Identifier  string    `json:"Identifier"`
    21  	PushMessage string    `json:"PushMessage"`
    22  	Updated     time.Time `json:"Updated"`
    23  	Published   time.Time `json:"Published"`
    24  	Headline    string    `json:"Headline"`
    25  	Preamble    string    `json:"Preamble"`
    26  	BodyText    string    `json:"BodyText"`
    27  	Area        []struct {
    28  		Type                string      `json:"Type"`
    29  		Description         string      `json:"Description"`
    30  		Coordinate          string      `json:"Coordinate"`
    31  		GeometryInformation interface{} `json:"GeometryInformation"`
    32  	} `json:"Area"`
    33  	Web        string        `json:"Web"`
    34  	Language   string        `json:"Language"`
    35  	Event      string        `json:"Event"`
    36  	SenderName string        `json:"SenderName"`
    37  	Push       bool          `json:"Push"`
    38  	BodyLinks  []interface{} `json:"BodyLinks"`
    39  	SourceID   int           `json:"SourceID"`
    40  	IsVma      bool          `json:"IsVma"`
    41  	IsTestVma  bool          `json:"IsTestVma"`
    42  }
    43  
    44  // Client holds or configuration
    45  type Client struct {
    46  	latitude  float64
    47  	longitude float64
    48  	radius    int
    49  	county    string
    50  	country   bool
    51  }
    52  
    53  // Item holds the interesting parts
    54  type Item struct {
    55  	PushMessage string
    56  	HeadLine    string
    57  	SenderName  string
    58  	Country     bool
    59  	County      bool
    60  	Distance    float64
    61  	Updated     time.Time
    62  }
    63  
    64  // NewClient returns a new Client
    65  func NewClient(latitude, longitude float64, radius int, county string, country bool) *Client {
    66  	return &Client{
    67  		latitude:  latitude,
    68  		longitude: longitude,
    69  		radius:    radius,
    70  		county:    county,
    71  		country:   country,
    72  	}
    73  
    74  }
    75  
    76  // getKrisinformation - return items that match either country, county or a radius
    77  // Priority:
    78  //   - Country
    79  //   - County
    80  //   - Region
    81  func (c *Client) getKrisinformation() (items []Item, err error) {
    82  	resp, err := http.Get(krisinformationAPI)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	defer func() { _ = resp.Body.Close() }()
    87  
    88  	var data Krisinformation
    89  	err = utils.ParseJSON(&data, resp.Body)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	for i := range data {
    95  		for a := range data[i].Area {
    96  			// Country wide events
    97  			if c.country && data[i].Area[a].Type == "Country" {
    98  				item := Item{
    99  					PushMessage: data[i].PushMessage,
   100  					HeadLine:    data[i].Headline,
   101  					SenderName:  data[i].SenderName,
   102  					Country:     true,
   103  					Updated:     data[i].Updated,
   104  				}
   105  				items = append(items, item)
   106  				continue
   107  			}
   108  
   109  			// County specific events
   110  			if c.county != "" && data[i].Area[a].Type == "County" {
   111  				// We look for county in description
   112  				if strings.Contains(
   113  					strings.ToLower(data[i].Area[a].Description),
   114  					strings.ToLower(c.county),
   115  				) {
   116  					item := Item{
   117  						PushMessage: data[i].PushMessage,
   118  						HeadLine:    data[i].Headline,
   119  						SenderName:  data[i].SenderName,
   120  						County:      true,
   121  						Updated:     data[i].Updated,
   122  					}
   123  					items = append(items, item)
   124  					continue
   125  				}
   126  			}
   127  
   128  			if c.radius != -1 {
   129  				coords := data[i].Area[a].Coordinate
   130  				if coords == "" {
   131  					continue
   132  				}
   133  				buf := strings.Split(coords, " ")
   134  				latlon := strings.Split(buf[0], ",")
   135  				kris_latitude, err := strconv.ParseFloat(latlon[0], 32)
   136  				if err != nil {
   137  					return nil, err
   138  				}
   139  
   140  				kris_longitude, err := strconv.ParseFloat(latlon[1], 32)
   141  				if err != nil {
   142  					return nil, err
   143  				}
   144  
   145  				distance := DistanceInMeters(kris_latitude, kris_longitude, c.latitude, c.longitude)
   146  				logger.Log(fmt.Sprintf("Distance: %f", distance/1000)) // KM
   147  				if distance < float64(c.radius) {
   148  					item := Item{
   149  						PushMessage: data[i].PushMessage,
   150  						HeadLine:    data[i].Headline,
   151  						SenderName:  data[i].SenderName,
   152  						Distance:    distance,
   153  						Updated:     data[i].Updated,
   154  					}
   155  					items = append(items, item)
   156  				}
   157  
   158  			}
   159  		}
   160  	}
   161  
   162  	return items, nil
   163  }
   164  
   165  // haversin(θ) function
   166  func hsin(theta float64) float64 {
   167  	return math.Pow(math.Sin(theta/2), 2)
   168  }
   169  
   170  // Distance function returns the distance (in meters) between two points of
   171  //
   172  //	a given longitude and latitude relatively accurately (using a spherical
   173  //	approximation of the Earth) through the Haversin Distance Formula for
   174  //	great arc distance on a sphere with accuracy for small distances
   175  //
   176  // point coordinates are supplied in degrees and converted into rad. in the func
   177  //
   178  // http://en.wikipedia.org/wiki/Haversine_formula
   179  func DistanceInMeters(lat1, lon1, lat2, lon2 float64) float64 {
   180  	// convert to radians
   181  	// must cast radius as float to multiply later
   182  	var la1, lo1, la2, lo2, r float64
   183  	la1 = lat1 * math.Pi / 180
   184  	lo1 = lon1 * math.Pi / 180
   185  	la2 = lat2 * math.Pi / 180
   186  	lo2 = lon2 * math.Pi / 180
   187  
   188  	r = 6378100 // Earth radius in METERS
   189  
   190  	// calculate
   191  	h := hsin(la2-la1) + math.Cos(la1)*math.Cos(la2)*hsin(lo2-lo1)
   192  
   193  	return 2 * r * math.Asin(math.Sqrt(h))
   194  }