eyeleng 1.0.9 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -197,19 +197,24 @@ Common commands:
197
197
  Important options:
198
198
 
199
199
  ```text
200
- --all print input and inferred triples
201
- --check parse and analyze only
202
- --strict warnings become fatal
203
- --deps print dependency edges and layers
204
- --trace show rule firings
205
- --stats show iteration and rule counts
206
- --json structured output
207
- --query run a raw body pattern over the closure
208
- --query-file FILE read a query body from a file
209
- --max-iterations N recursive-layer fixpoint safety guard
210
- --no-imports parse imports but do not load them
211
- --rdf-messages parse input as an RDF Message Log
212
- --stream-messages replay RDF Message Log envelopes
200
+ --all print the full closure, including input facts
201
+ --json print JSON instead of compact triples/bindings
202
+ --trace print derivation trace to stderr, or include it in JSON
203
+ --stats print iteration and triple counts to stderr
204
+ --check parse and analyze only; do not run rules
205
+ --strict treat static warnings as errors
206
+ --deps print rule dependency edges during --check
207
+ --query TEXT run a raw SRL body pattern over the closure
208
+ --query-file FILE read a raw SRL body pattern from a file
209
+ --max-iterations N stop after N fixpoint iterations within a recursive layer
210
+ --no-imports parse IMPORTS/owl:imports but do not load imported rule sets
211
+ --rdf-messages parse input as an RDF Message Log
212
+ --stream-messages replay RDF Message Log envelopes
213
+ --include-message-facts include payload facts while parsing RDF Message Logs
214
+ --syntax MODE use srl, rdf, or auto syntax detection (default auto)
215
+ --ruleset TERM in RDF syntax, run only the selected srl:RuleSet
216
+ --version print version
217
+ -h, --help print help
213
218
  ```
214
219
 
215
220
  ## Public API
@@ -0,0 +1,289 @@
1
+ # GPS — generic route-planning over a road graph.
2
+ #
3
+ # Adapted from eyeling/examples/gps.n3:
4
+ # https://raw.githubusercontent.com/eyereasoner/eyeling/refs/heads/main/examples/gps.n3
5
+ #
6
+ # The facts below provide a tiny western-Belgium road map and one route query.
7
+ # The rules are generic: they build simple paths from any gps:Drive graph,
8
+ # compute accumulated duration/cost/belief/comfort metrics, mark dominated
9
+ # completed paths, and recommend each non-dominated completed path.
10
+
11
+ PREFIX log: <http://www.w3.org/2000/10/swap/log#>
12
+ PREFIX gps: <http://example.org/gps#>
13
+ PREFIX : <http://example.org/gps-demo/>
14
+ PREFIX path: <http://example.org/gps-demo/path/>
15
+
16
+ DATA {
17
+ :Gent gps:label "Gent" .
18
+ :Brugge gps:label "Brugge" .
19
+ :Kortrijk gps:label "Kortrijk" .
20
+ :Oostende gps:label "Oostende" .
21
+
22
+ :i1 gps:location :Gent .
23
+
24
+ :question a gps:RouteQuery ;
25
+ gps:traveller :i1 ;
26
+ gps:to :Oostende ;
27
+ gps:maxLegs 4 ;
28
+ gps:text "Which route should we take from Gent to Oostende?" .
29
+
30
+ :drive_gent_brugge a gps:Drive ;
31
+ gps:from :Gent ; gps:to :Brugge ;
32
+ gps:actionLabel "drive_gent_brugge" ;
33
+ gps:duration 1500.0 ; gps:cost 0.006 ; gps:belief 0.96 ; gps:comfort 0.99 .
34
+
35
+ :drive_gent_kortrijk a gps:Drive ;
36
+ gps:from :Gent ; gps:to :Kortrijk ;
37
+ gps:actionLabel "drive_gent_kortrijk" ;
38
+ gps:duration 1600.0 ; gps:cost 0.007 ; gps:belief 0.96 ; gps:comfort 0.99 .
39
+
40
+ :drive_kortrijk_brugge a gps:Drive ;
41
+ gps:from :Kortrijk ; gps:to :Brugge ;
42
+ gps:actionLabel "drive_kortrijk_brugge" ;
43
+ gps:duration 1600.0 ; gps:cost 0.007 ; gps:belief 0.96 ; gps:comfort 0.99 .
44
+
45
+ :drive_brugge_oostende a gps:Drive ;
46
+ gps:from :Brugge ; gps:to :Oostende ;
47
+ gps:actionLabel "drive_brugge_oostende" ;
48
+ gps:duration 900.0 ; gps:cost 0.004 ; gps:belief 0.98 ; gps:comfort 1.0 .
49
+ }
50
+
51
+ # Resolve the traveller's current location and human-readable endpoint labels.
52
+ RULE {
53
+ ?query gps:from ?source ;
54
+ gps:fromLabel ?sourceLabel ;
55
+ gps:toLabel ?destinationLabel .
56
+ }
57
+ WHERE {
58
+ ?query a gps:RouteQuery ;
59
+ gps:traveller ?traveller ;
60
+ gps:to ?destination .
61
+ ?traveller gps:location ?source .
62
+ ?source gps:label ?sourceLabel .
63
+ ?destination gps:label ?destinationLabel .
64
+ }
65
+
66
+ # Seed one-leg candidate paths from the query source.
67
+ RULE {
68
+ ?path a gps:CandidatePath ;
69
+ gps:query ?query ;
70
+ gps:last ?next ;
71
+ gps:legs 1 ;
72
+ gps:pathKey ?pathKey ;
73
+ gps:routeLabel ?routeLabel ;
74
+ gps:actions ?actions ;
75
+ gps:pathIRI ?path ;
76
+ gps:duration ?duration ;
77
+ gps:cost ?cost ;
78
+ gps:belief ?belief ;
79
+ gps:comfort ?comfort .
80
+ }
81
+ WHERE {
82
+ ?query a gps:RouteQuery ;
83
+ gps:from ?source .
84
+ ?drive a gps:Drive ;
85
+ gps:from ?source ;
86
+ gps:to ?next ;
87
+ gps:actionLabel ?action ;
88
+ gps:duration ?duration ;
89
+ gps:cost ?cost ;
90
+ gps:belief ?belief ;
91
+ gps:comfort ?comfort .
92
+ FILTER(!sameTerm(?source, ?next))
93
+ ?source gps:label ?sourceLabel .
94
+ ?next gps:label ?nextLabel .
95
+ SET(?pathKey := CONCAT("|", STR(?source), "|", STR(?next), "|"))
96
+ SET(?routeLabel := CONCAT(?sourceLabel, " → ", ?nextLabel))
97
+ SET(?actions := ?action)
98
+ SET(?pathSlug1 := REPLACE(?routeLabel, " → ", "_", "g"))
99
+ SET(?pathSlug := REPLACE(?pathSlug1, "[^A-Za-z0-9_-]", "_", "g"))
100
+ SET(?path := IRI(CONCAT("http://example.org/gps-demo/path/", ?pathSlug)))
101
+ }
102
+
103
+ # Extend candidate paths through the graph while staying within the query's
104
+ # leg bound and avoiding revisiting already-seen locations.
105
+ RULE {
106
+ ?nextPath a gps:CandidatePath ;
107
+ gps:query ?query ;
108
+ gps:last ?next ;
109
+ gps:legs ?nextLegs ;
110
+ gps:pathKey ?nextPathKey ;
111
+ gps:routeLabel ?nextRouteLabel ;
112
+ gps:actions ?nextActions ;
113
+ gps:pathIRI ?nextPath ;
114
+ gps:duration ?nextDuration ;
115
+ gps:cost ?nextCost ;
116
+ gps:belief ?nextBelief ;
117
+ gps:comfort ?nextComfort .
118
+ }
119
+ WHERE {
120
+ ?path a gps:CandidatePath ;
121
+ gps:query ?query ;
122
+ gps:last ?current ;
123
+ gps:legs ?legs ;
124
+ gps:pathKey ?pathKey ;
125
+ gps:routeLabel ?routeLabel ;
126
+ gps:actions ?actions ;
127
+ gps:pathIRI ?path ;
128
+ gps:duration ?duration ;
129
+ gps:cost ?cost ;
130
+ gps:belief ?belief ;
131
+ gps:comfort ?comfort .
132
+ ?query gps:to ?destination ;
133
+ gps:maxLegs ?maxLegs .
134
+ FILTER(!sameTerm(?current, ?destination))
135
+ FILTER(?legs < ?maxLegs)
136
+ ?drive a gps:Drive ;
137
+ gps:from ?current ;
138
+ gps:to ?next ;
139
+ gps:actionLabel ?action ;
140
+ gps:duration ?legDuration ;
141
+ gps:cost ?legCost ;
142
+ gps:belief ?legBelief ;
143
+ gps:comfort ?legComfort .
144
+ FILTER(!sameTerm(?current, ?next))
145
+ SET(?needle := CONCAT("|", STR(?next), "|"))
146
+ FILTER(!CONTAINS(?pathKey, ?needle))
147
+ ?next gps:label ?nextLabel .
148
+ SET(?nextLegs := ?legs + 1)
149
+ SET(?nextPathKey := CONCAT(?pathKey, STR(?next), "|"))
150
+ SET(?nextRouteLabel := CONCAT(?routeLabel, " → ", ?nextLabel))
151
+ SET(?nextActions := CONCAT(?actions, ", ", ?action))
152
+ SET(?nextDuration := ?duration + ?legDuration)
153
+ SET(?nextCost := ?cost + ?legCost)
154
+ SET(?nextBelief := ?belief * ?legBelief)
155
+ SET(?nextComfort := ?comfort * ?legComfort)
156
+ SET(?nextPathSlug1 := REPLACE(?nextRouteLabel, " → ", "_", "g"))
157
+ SET(?nextPathSlug := REPLACE(?nextPathSlug1, "[^A-Za-z0-9_-]", "_", "g"))
158
+ SET(?nextPath := IRI(CONCAT("http://example.org/gps-demo/path/", ?nextPathSlug)))
159
+ }
160
+
161
+ # A completed path is any candidate that reaches the query destination.
162
+ RULE {
163
+ ?path a gps:CompletedPath .
164
+ ?query gps:hasCandidatePath ?path .
165
+ }
166
+ WHERE {
167
+ ?query a gps:RouteQuery ;
168
+ gps:to ?destination .
169
+ ?path a gps:CandidatePath ;
170
+ gps:query ?query ;
171
+ gps:last ?destination .
172
+ }
173
+
174
+ # Generic dominance: another completed path for the same query is better when
175
+ # it is faster, cheaper, more reliable, and at least as comfortable.
176
+ RULE {
177
+ ?path gps:dominatedBy ?better .
178
+ }
179
+ WHERE {
180
+ ?path a gps:CompletedPath ;
181
+ gps:query ?query ;
182
+ gps:duration ?duration ;
183
+ gps:cost ?cost ;
184
+ gps:belief ?belief ;
185
+ gps:comfort ?comfort .
186
+ ?better a gps:CompletedPath ;
187
+ gps:query ?query ;
188
+ gps:duration ?betterDuration ;
189
+ gps:cost ?betterCost ;
190
+ gps:belief ?betterBelief ;
191
+ gps:comfort ?betterComfort .
192
+ FILTER(!sameTerm(?path, ?better))
193
+ FILTER(?betterDuration < ?duration)
194
+ FILTER(?betterCost < ?cost)
195
+ FILTER(?betterBelief > ?belief)
196
+ FILTER(?betterComfort >= ?comfort)
197
+ }
198
+
199
+ # Recommend every completed path that is not dominated by another completed
200
+ # path. In this data set there is one such path, but the rule itself is not
201
+ # tied to Gent, Brugge, Kortrijk, or Oostende.
202
+ RULE {
203
+ ?query gps:recommendedPath ?path .
204
+ ?path gps:recommended true .
205
+ }
206
+ WHERE {
207
+ ?path a gps:CompletedPath ;
208
+ gps:query ?query .
209
+ NOT { ?path gps:dominatedBy ?other }
210
+ }
211
+
212
+ # Present the recommendation as compact, readable facts first. The path-search
213
+ # bookkeeping remains in the inferred graph under path:* resources for users who
214
+ # want to inspect how the answer was found.
215
+ RULE {
216
+ :answer a gps:RouteRecommendation ;
217
+ gps:question ?questionText ;
218
+ gps:route ?bestLabel ;
219
+ gps:routeActions ?actions ;
220
+ gps:routeDuration ?duration ;
221
+ gps:routeCost ?displayCost ;
222
+ gps:routeBelief ?belief ;
223
+ gps:routeComfort ?comfort ;
224
+ gps:alternativeRoute ?alternativeLabel ;
225
+ gps:alternativeRouteActions ?alternativeActions ;
226
+ gps:alternativeRouteDuration ?alternativeDuration ;
227
+ gps:alternativeRouteCost ?alternativeDisplayCost ;
228
+ gps:alternativeRouteBelief ?alternativeBelief ;
229
+ gps:alternativeRouteComfort ?alternativeComfort ;
230
+ gps:reasonWhy ?reasonWhy ;
231
+ log:outputString ?block .
232
+ }
233
+ WHERE {
234
+ ?query a gps:RouteQuery ;
235
+ gps:text ?questionText ;
236
+ gps:fromLabel ?sourceLabel ;
237
+ gps:toLabel ?destinationLabel ;
238
+ gps:recommendedPath ?bestPath .
239
+ ?bestPath gps:routeLabel ?bestLabel ;
240
+ gps:actions ?actions ;
241
+ gps:duration ?duration ;
242
+ gps:cost ?cost ;
243
+ gps:belief ?belief ;
244
+ gps:comfort ?comfort .
245
+ ?alternative a gps:CompletedPath ;
246
+ gps:query ?query ;
247
+ gps:dominatedBy ?bestPath ;
248
+ gps:routeLabel ?alternativeLabel ;
249
+ gps:actions ?alternativeActions ;
250
+ gps:duration ?alternativeDuration ;
251
+ gps:cost ?alternativeCost ;
252
+ gps:belief ?alternativeBelief ;
253
+ gps:comfort ?alternativeComfort .
254
+ SET(?displayCost := ROUND(?cost * 1000) / 1000)
255
+ SET(?alternativeDisplayCost := ROUND(?alternativeCost * 1000) / 1000)
256
+ SET(?outcome := CONCAT("Take ", ?bestLabel, "."))
257
+ SET(?reasonWhy := CONCAT(
258
+ "From ", ?sourceLabel, " to ", ?destinationLabel,
259
+ ", the planner found two routes in this small map. The direct route (", ?bestLabel,
260
+ ") takes ", STR(?duration), " seconds at cost ", STR(?displayCost),
261
+ ", with belief ", STR(?belief), " and comfort ", STR(?comfort),
262
+ ". The alternative (", ?alternativeLabel, ") takes ", STR(?alternativeDuration),
263
+ " seconds at cost ", STR(?alternativeDisplayCost), ", with belief ", STR(?alternativeBelief),
264
+ " and comfort ", STR(?alternativeComfort),
265
+ ". So the direct route is faster, cheaper, more reliable, and slightly more comfortable."
266
+ ))
267
+ SET(?block := CONCAT(
268
+ "GPS — Generic route planning\n\n",
269
+ "## Question\n", ?questionText, "\n\n",
270
+ "## Answer\n", ?outcome, "\n\n",
271
+ "## Recommended Route\n", ?bestLabel, "\n",
272
+ "Actions: ", ?actions, "\n",
273
+ "Duration: ", STR(?duration), " seconds\n",
274
+ "Cost: ", STR(?displayCost), "\n",
275
+ "Belief: ", STR(?belief), "\n",
276
+ "Comfort: ", STR(?comfort), "\n\n",
277
+ "## Alternative Route\n", ?alternativeLabel, "\n",
278
+ "Actions: ", ?alternativeActions, "\n",
279
+ "Duration: ", STR(?alternativeDuration), " seconds\n",
280
+ "Cost: ", STR(?alternativeDisplayCost), "\n",
281
+ "Belief: ", STR(?alternativeBelief), "\n",
282
+ "Comfort: ", STR(?alternativeComfort), "\n\n",
283
+ "## Reason Why\n", ?reasonWhy, "\n\n",
284
+ "## Check\n",
285
+ "C1 OK - complete paths are discovered by chaining gps:Drive facts.\n",
286
+ "C2 OK - cycles are avoided with the generated path key.\n",
287
+ "C3 OK - dominated complete paths are rejected before recommendation.\n"
288
+ ))
289
+ }
@@ -0,0 +1,86 @@
1
+ :answer a gps:RouteRecommendation .
2
+ :answer gps:alternativeRoute "Gent → Kortrijk → Brugge → Oostende" .
3
+ :answer gps:alternativeRouteActions "drive_gent_kortrijk, drive_kortrijk_brugge, drive_brugge_oostende" .
4
+ :answer gps:alternativeRouteBelief 0.903168 .
5
+ :answer gps:alternativeRouteComfort 0.9801 .
6
+ :answer gps:alternativeRouteCost 0.018 .
7
+ :answer gps:alternativeRouteDuration 4100 .
8
+ :answer gps:question "Which route should we take from Gent to Oostende?" .
9
+ :answer gps:reasonWhy "From Gent to Oostende, the planner found two routes in this small map. The direct route (Gent → Brugge → Oostende) takes 2400 seconds at cost 0.01, with belief 0.9408 and comfort 0.99. The alternative (Gent → Kortrijk → Brugge → Oostende) takes 4100 seconds at cost 0.018, with belief 0.903168 and comfort 0.9801. So the direct route is faster, cheaper, more reliable, and slightly more comfortable." .
10
+ :answer gps:route "Gent → Brugge → Oostende" .
11
+ :answer gps:routeActions "drive_gent_brugge, drive_brugge_oostende" .
12
+ :answer gps:routeBelief 0.9408 .
13
+ :answer gps:routeComfort 0.99 .
14
+ :answer gps:routeCost 0.01 .
15
+ :answer gps:routeDuration 2400 .
16
+ :answer log:outputString "GPS — Generic route planning\n\n## Question\nWhich route should we take from Gent to Oostende?\n\n## Answer\nTake Gent → Brugge → Oostende.\n\n## Recommended Route\nGent → Brugge → Oostende\nActions: drive_gent_brugge, drive_brugge_oostende\nDuration: 2400 seconds\nCost: 0.01\nBelief: 0.9408\nComfort: 0.99\n\n## Alternative Route\nGent → Kortrijk → Brugge → Oostende\nActions: drive_gent_kortrijk, drive_kortrijk_brugge, drive_brugge_oostende\nDuration: 4100 seconds\nCost: 0.018\nBelief: 0.903168\nComfort: 0.9801\n\n## Reason Why\nFrom Gent to Oostende, the planner found two routes in this small map. The direct route (Gent → Brugge → Oostende) takes 2400 seconds at cost 0.01, with belief 0.9408 and comfort 0.99. The alternative (Gent → Kortrijk → Brugge → Oostende) takes 4100 seconds at cost 0.018, with belief 0.903168 and comfort 0.9801. So the direct route is faster, cheaper, more reliable, and slightly more comfortable.\n\n## Check\nC1 OK - complete paths are discovered by chaining gps:Drive facts.\nC2 OK - cycles are avoided with the generated path key.\nC3 OK - dominated complete paths are rejected before recommendation.\n" .
17
+ :question gps:from :Gent .
18
+ :question gps:fromLabel "Gent" .
19
+ :question gps:hasCandidatePath path:Gent_Brugge_Oostende .
20
+ :question gps:hasCandidatePath path:Gent_Kortrijk_Brugge_Oostende .
21
+ :question gps:recommendedPath path:Gent_Brugge_Oostende .
22
+ :question gps:toLabel "Oostende" .
23
+ path:Gent_Brugge a gps:CandidatePath .
24
+ path:Gent_Brugge gps:actions "drive_gent_brugge" .
25
+ path:Gent_Brugge gps:belief 0.96 .
26
+ path:Gent_Brugge gps:comfort 0.99 .
27
+ path:Gent_Brugge gps:cost 0.006 .
28
+ path:Gent_Brugge gps:duration 1500 .
29
+ path:Gent_Brugge gps:last :Brugge .
30
+ path:Gent_Brugge gps:legs 1 .
31
+ path:Gent_Brugge gps:pathIRI path:Gent_Brugge .
32
+ path:Gent_Brugge gps:pathKey "|http://example.org/gps-demo/Gent|http://example.org/gps-demo/Brugge|" .
33
+ path:Gent_Brugge gps:query :question .
34
+ path:Gent_Brugge gps:routeLabel "Gent → Brugge" .
35
+ path:Gent_Brugge_Oostende a gps:CandidatePath .
36
+ path:Gent_Brugge_Oostende a gps:CompletedPath .
37
+ path:Gent_Brugge_Oostende gps:actions "drive_gent_brugge, drive_brugge_oostende" .
38
+ path:Gent_Brugge_Oostende gps:belief 0.9408 .
39
+ path:Gent_Brugge_Oostende gps:comfort 0.99 .
40
+ path:Gent_Brugge_Oostende gps:cost 0.01 .
41
+ path:Gent_Brugge_Oostende gps:duration 2400 .
42
+ path:Gent_Brugge_Oostende gps:last :Oostende .
43
+ path:Gent_Brugge_Oostende gps:legs 2 .
44
+ path:Gent_Brugge_Oostende gps:pathIRI path:Gent_Brugge_Oostende .
45
+ path:Gent_Brugge_Oostende gps:pathKey "|http://example.org/gps-demo/Gent|http://example.org/gps-demo/Brugge|http://example.org/gps-demo/Oostende|" .
46
+ path:Gent_Brugge_Oostende gps:query :question .
47
+ path:Gent_Brugge_Oostende gps:recommended true .
48
+ path:Gent_Brugge_Oostende gps:routeLabel "Gent → Brugge → Oostende" .
49
+ path:Gent_Kortrijk a gps:CandidatePath .
50
+ path:Gent_Kortrijk gps:actions "drive_gent_kortrijk" .
51
+ path:Gent_Kortrijk gps:belief 0.96 .
52
+ path:Gent_Kortrijk gps:comfort 0.99 .
53
+ path:Gent_Kortrijk gps:cost 0.007 .
54
+ path:Gent_Kortrijk gps:duration 1600 .
55
+ path:Gent_Kortrijk gps:last :Kortrijk .
56
+ path:Gent_Kortrijk gps:legs 1 .
57
+ path:Gent_Kortrijk gps:pathIRI path:Gent_Kortrijk .
58
+ path:Gent_Kortrijk gps:pathKey "|http://example.org/gps-demo/Gent|http://example.org/gps-demo/Kortrijk|" .
59
+ path:Gent_Kortrijk gps:query :question .
60
+ path:Gent_Kortrijk gps:routeLabel "Gent → Kortrijk" .
61
+ path:Gent_Kortrijk_Brugge a gps:CandidatePath .
62
+ path:Gent_Kortrijk_Brugge gps:actions "drive_gent_kortrijk, drive_kortrijk_brugge" .
63
+ path:Gent_Kortrijk_Brugge gps:belief 0.9216 .
64
+ path:Gent_Kortrijk_Brugge gps:comfort 0.9801 .
65
+ path:Gent_Kortrijk_Brugge gps:cost 0.014 .
66
+ path:Gent_Kortrijk_Brugge gps:duration 3200 .
67
+ path:Gent_Kortrijk_Brugge gps:last :Brugge .
68
+ path:Gent_Kortrijk_Brugge gps:legs 2 .
69
+ path:Gent_Kortrijk_Brugge gps:pathIRI path:Gent_Kortrijk_Brugge .
70
+ path:Gent_Kortrijk_Brugge gps:pathKey "|http://example.org/gps-demo/Gent|http://example.org/gps-demo/Kortrijk|http://example.org/gps-demo/Brugge|" .
71
+ path:Gent_Kortrijk_Brugge gps:query :question .
72
+ path:Gent_Kortrijk_Brugge gps:routeLabel "Gent → Kortrijk → Brugge" .
73
+ path:Gent_Kortrijk_Brugge_Oostende a gps:CandidatePath .
74
+ path:Gent_Kortrijk_Brugge_Oostende a gps:CompletedPath .
75
+ path:Gent_Kortrijk_Brugge_Oostende gps:actions "drive_gent_kortrijk, drive_kortrijk_brugge, drive_brugge_oostende" .
76
+ path:Gent_Kortrijk_Brugge_Oostende gps:belief 0.903168 .
77
+ path:Gent_Kortrijk_Brugge_Oostende gps:comfort 0.9801 .
78
+ path:Gent_Kortrijk_Brugge_Oostende gps:cost 0.018000000000000002 .
79
+ path:Gent_Kortrijk_Brugge_Oostende gps:dominatedBy path:Gent_Brugge_Oostende .
80
+ path:Gent_Kortrijk_Brugge_Oostende gps:duration 4100 .
81
+ path:Gent_Kortrijk_Brugge_Oostende gps:last :Oostende .
82
+ path:Gent_Kortrijk_Brugge_Oostende gps:legs 3 .
83
+ path:Gent_Kortrijk_Brugge_Oostende gps:pathIRI path:Gent_Kortrijk_Brugge_Oostende .
84
+ path:Gent_Kortrijk_Brugge_Oostende gps:pathKey "|http://example.org/gps-demo/Gent|http://example.org/gps-demo/Kortrijk|http://example.org/gps-demo/Brugge|http://example.org/gps-demo/Oostende|" .
85
+ path:Gent_Kortrijk_Brugge_Oostende gps:query :question .
86
+ path:Gent_Kortrijk_Brugge_Oostende gps:routeLabel "Gent → Kortrijk → Brugge → Oostende" .