| | 149 | |
| | 150 | |
| | 151 | ** Наоѓање на најблизок возач (K-Nearest Neighbors) ** |
| | 152 | |
| | 153 | При креирањето на понуда од страна на диспечерот, системот автоматски го бара најблискиот слободен возач од таа компанија. За оваа цел се користи PostGIS операторот `<->`, кој го пресметува просторното растојание користејќи го GiST индексот. Ова овозможува пронаоѓање на возачот во `O(1)` односно константно време, без да се скенираат сите возачи. |
| | 154 | |
| | 155 | {{{ |
| | 156 | create or replace procedure create_offer( |
| | 157 | v_request_id int4, |
| | 158 | v_dispatcher_user_id int4, |
| | 159 | v_price numeric(19, 2), |
| | 160 | v_currency_catalog_id int4, |
| | 161 | v_eta timestamp |
| | 162 | ) |
| | 163 | language plpgsql |
| | 164 | AS |
| | 165 | $$ |
| | 166 | declare |
| | 167 | v_driver_user_id int4; |
| | 168 | v_dispatcher_company_id int4; |
| | 169 | v_start_position geometry; |
| | 170 | v_customer_user_id int4; |
| | 171 | begin |
| | 172 | if not exists(select * from request where request.id = v_request_id and request.status = 'pending') then |
| | 173 | raise exception 'Request with id % and pending status does not exist', v_request_id; |
| | 174 | end if; |
| | 175 | if v_price <= 0 then |
| | 176 | raise exception 'Price has to be greater than 0'; |
| | 177 | end if; |
| | 178 | if v_eta <= now() then |
| | 179 | raise exception 'ETA cannot be lower than the time of creation'; |
| | 180 | end if; |
| | 181 | |
| | 182 | v_customer_user_id := (select customer_user_id from request where request.id = v_request_id); |
| | 183 | |
| | 184 | v_dispatcher_company_id := (select eh.company_id |
| | 185 | from dispatcher d |
| | 186 | join employmenthistory eh on eh.employee_user_id = d.user_id and |
| | 187 | (eh.end_date is null or eh.end_date > now()) |
| | 188 | where d.user_id = v_dispatcher_user_id |
| | 189 | limit 1); |
| | 190 | |
| | 191 | if v_dispatcher_company_id is null then |
| | 192 | raise exception 'Dispatcher % is not assigned to a company', |
| | 193 | v_dispatcher_user_id; |
| | 194 | end if; |
| | 195 | |
| | 196 | v_start_position := (select start_location from request where id = v_request_id); |
| | 197 | |
| | 198 | v_driver_user_id := (select d.user_id |
| | 199 | from driver d |
| | 200 | join driver_vehicle dc on d.user_id = dc.id_driver |
| | 201 | join employmenthistory eh |
| | 202 | on eh.employee_user_id = d.user_id and (eh.end_date is null or |
| | 203 | eh.end_date > now()) |
| | 204 | where dc.time_to is null |
| | 205 | and eh.company_id = v_dispatcher_company_id |
| | 206 | and d.location is not null |
| | 207 | order by d.location <-> v_start_position |
| | 208 | limit 1); |
| | 209 | |
| | 210 | if v_driver_user_id is null then |
| | 211 | raise exception 'No available drivers found for request %', v_request_id; |
| | 212 | end if; |
| | 213 | |
| | 214 | insert into offer(status, created_at, request_id, dispatcher_user_id, driver_user_id, price, currency_catalog_id, |
| | 215 | eta, customer_user_id) |
| | 216 | values ('pending', now(), v_request_id, v_dispatcher_user_id, v_driver_user_id, |
| | 217 | v_price, v_currency_catalog_id, v_eta, v_customer_user_id); |
| | 218 | commit; |
| | 219 | end; |
| | 220 | $$; |
| | 221 | }}} |
| | 222 | |
| | 223 | |
| | 224 | ** Lambda Архитектура и 4D Траектории на возење ** |
| | 225 | |
| | 226 | За следење на возилата во реално време, воведена е Lambda архитектура. Табелата location служи како "Hot storage" каде што се запишуваат илјадници GPS точки додека возилото се движи. Кога возењето ќе заврши, се активира тригер кој ги собира сите точки и ги компресира во една 4-димензионална линија (LineStringZM – каде Z е брзината, а M е Unix времето) во табелата ride ("Cold storage"). Откако траекторијата е зачувана, сировите точки се бришат за да се ослободи меморија. |
| | 227 | |
| | 228 | {{{ |
| | 229 | alter table location |
| | 230 | add column speed numeric(5,2) default 0; |
| | 231 | |
| | 232 | alter table ride |
| | 233 | add column route_path geometry(LineStringZM, 4326); |
| | 234 | |
| | 235 | create or replace function archive_ride_trajectory() |
| | 236 | returns trigger |
| | 237 | language plpgsql |
| | 238 | as $$ |
| | 239 | begin |
| | 240 | if NEW.status = 'completed' and OLD.status != 'completed' then |
| | 241 | NEW.route_path := ( |
| | 242 | select ST_MakeLine( |
| | 243 | ST_MakePoint( |
| | 244 | l.longitude, |
| | 245 | l.latitude, |
| | 246 | COALESCE(l.speed, 0), |
| | 247 | extract(epoch from l.timestamp) |
| | 248 | ) |
| | 249 | order by l.timestamp |
| | 250 | ) |
| | 251 | from location l |
| | 252 | where l.ride_id = NEW.id |
| | 253 | ); |
| | 254 | |
| | 255 | delete from location where ride_id = NEW.id; |
| | 256 | end if; |
| | 257 | |
| | 258 | return NEW; |
| | 259 | end; |
| | 260 | $$; |
| | 261 | |
| | 262 | create trigger trigger_archive_trajectory |
| | 263 | before update on ride |
| | 264 | for each row |
| | 265 | execute function archive_ride_trajectory(); |
| | 266 | |
| | 267 | update ride r |
| | 268 | set route_path = ( |
| | 269 | select ST_MakeLine( |
| | 270 | ST_MakePoint( |
| | 271 | l.longitude, |
| | 272 | l.latitude, |
| | 273 | COALESCE(l.speed, 0), |
| | 274 | extract(epoch from l.timestamp) |
| | 275 | ) |
| | 276 | order by l.timestamp |
| | 277 | ) |
| | 278 | from location l |
| | 279 | where l.ride_id = r.id |
| | 280 | ) |
| | 281 | where r.status = 'completed' and r.route_path is null; |
| | 282 | |
| | 283 | delete from location |
| | 284 | where ride_id in (select id from ride where status = 'completed'); |
| | 285 | }}} |